페이지

2025년 11월 13일 목요일

기업 폐쇄망에서 보험중개 지원 Agentic RAG 구현하기 – part 2

 Part 1 에서 언급한 10가지 고려사항들 중에서 프로덕션을 염두에 둘 때 중요한 요소 중 하나가 지속성(persistency)였다.

에이전트 패러독스

에이전트가 강력해 질수록, 재앙적으로 실패한 가능성도 높아진다. 이러한 실패로 이어지는 주요 요인중 하나는 지속성이다. 에이전트의 파이프라인 오케스트레이션으로 랭그래프를 채택할 경우, 랭그래프의 state 가 늘어나면 메모리 제약이 뒤따르고,, 에이전트 수행이 거듭될 수록 비록 랭그래프가 많은 부분 세션 유지나 지속성을 담보하기는 하지만, Open Weight LLM 을 폐쇄환경에서 사용하는데 있어서 지속성을 담보하기 위해서는 보강이 필요하였다.

1. 저장소 persistency:

이전에 사용하던 벡터DB를 우선 엔터프라이즈 급 오픈소스 데이터베이스로 변경했다. 우리 회사의 LLMOps 인 치타의 persistence 영역에 저장하고 기업내 동시 사용자가 성능에 지장 없이 에이전트를 운영할 수 있는 벡터 검색능력과 관계형 DB를 함꼐 지원하는 데이터베이스를 채택했다. 이는 문서와 메모리 이력을 텍스트 + 벡터 로 저장하여 유사도로 검색하기위해 유용했다.

2. 대화 persistency:

에이전트의 수행 횟수와 더불어 랭그래프의 state 가 끌고다니는 메세지들은 누적되고 검색된 문서들은 차곡차곡 쌓이며 tool 은 이들을 결합해서 출력한다. 특히 Naive RAG 에서 Agentic RAG 으로 진화해갈때 처음 드는 생각이 여러 사용들이 지속적인 agent call 을 사용하면 메모리를 어떻게 감당할까 였다. 임시 메모리에서 장기 메모리인 데이터베이스로 저장하고 필요할때 불러오는 방식을 생각했는데 문헌을 찾아보며 생각이 바뀌었다. 메모리가 상호작용 이력을 기억하여 지속적으로 학습하고 향상하는 시스템으로의 기반이 된다는 점이다.

Agentic Memory Management:

최근 LLM의 context window 크기가 증가하는 추세로 프롬프트를 포함한 전체를 단기 메모리를 통해 사용하는 CAG 방식도 있을 수 있고 (구글 Gemini 가 글을 쓰는 시점에 추가 노력없이 자료만 넣어주면 자동으로 RAG를 만들어주는 솔루션을 발표했다), 벡터DB로 임베딩하는 방식, 또는 구조화된 메모리 즉, 버퍼, 단기, 중기, archival 로 계층화된 메모리 구성등이 있지만, 최근 AI Agent 에서 부상하는 주제로는 agent 가 무엇을 기억하고 어떻게 메모리를 조직하며 언제 검색될지를 자체적으로 결정하는 Agentic 메모리 관리가 있다.

Agentic 메모리 주요 기능

1. Active Memory Management: AI 가 능동적으로 무엇을 기억하고, 어떻게 정보를 조직하며, 언제 접근할지를 결정
2. Persistence: 여러 대화 세션에 걸친 정보를 유지하는 능력
3. Selective Attention: 어떤 정보가 저장할 가치가 있는지 결정
4. Contextual Retrieval: 현재 필요에 기반한 연관 메모리 발견
5. Memory Integration: 기존 지식에 새 정보를 통합
6. Self-updating: 새 증거 출현 시 저장된 정보 변경 (지속학습을 위한 발판)

랭그래프의 Langmem

올해 2월에 LangGraph 는 Langmem 이라는 Agentic Memory 를 선보인다. 이를 통해 LLM agent 에게 장기 기억을 부여하고 대화들로 부터 중요 정보를 추출하는 tooling, 프롬프트 개선을 통한 agent 행위 최적화와 long-term 메모리 유지 등을 소개했다.  그러나 이 역시 OpenAI, Claude 같은 폐쇄형 모델들을 중심으로 소개되어 Open Weight LLM 으로의 구현은 어려웠다. Agentic Memory 관련 A-MEM, MemOS, Mem0 등의 논문들을 찾아보았고 Langmem 에서 개념으로 소개하는 계층형 메모리 구조가 IBM의 논문 에서 제시된것으로 많은 곳에서 인용되는 것을 보면서 원점에서 독자적인 Agentic Memory 를 개발해야겠다고 생각했다.


인지과학에서 본 사람의 메모리

논문에서는 사람의 기억 메카니즘을 4가지 유형으로 설명한다. 이를 AI 적용에 빗대어 대입해보면 아래과 같다.
1. Working Memory: 현재의 대화 문맥이 저장되는 단기 메모리
2. Episodic Memory: 과거 대화문맥, 사용자 응답, 실행결과 저장 (과거 경험 저장)
3. Semantic Memory: 사용자 선호도와 같이 정형 형식으로 저장된 사실. (과거 지식 저장)
4. Procedural Memory: Agent 가 어떻게 운영되었는지에 관한 정보 (Agent 행위를 안내할 프롬프트, 명령)

단기 메모리

LangGraph 는 내장된 persistence 층이 있어서 checkpointer 와 함께 graph 를 컴파일하면 state 가 변할 때 마다 thread 범위내 state 를 checkpointer 로 저장..InMemory 로 단기 메모리는 쉽게 정의와 사용이 가능. 사실 checkpointer 의 용도는 대화 중에 시스템이 중단되어 이전 정보 유실 가능성을 염두에 두고 사용하는데 단기, 장기 메모리가 어느 수준으로 유지된다면 굳이 checkpointer 를 turn on 해야 하는 생각도 든다.

장기 메모리

LangGraph Langmem:
LangMem 은 아래 그림 2와 같이 agent 가 활성 대화들 동안 tool 사용하여 저장/검색 할 수 있고 백그라운드 모드에서 일정 시간이 지나면 대화들로부터 지식을 자동으로 추출, 통합 및 변경하여 관리한다.

<그림 2. Langmem 저장 및 검색>


가젤 Agentic Memory:
저자가 개발한 가젤 Agentic 메모리는 단기메모리로는 LangGraph 의 InMemoryStore 를 그대로 차용하고, 장기메모리는 아래 그림 3과 같은 메모리 관리 생명주기에따라 백그라운드 모드에서 관리된다. LangGraph의 state 와 config 로 부터 대화데이터와 thread ID 등을 추출하여, 대화데이터는 우선 episodic 에 저장하고 각 카테고리별 키워드와 노출빈도에 따른 점수를 부여하여 가장 높은 점수 순으로 episodic 대화 중에서 주요 사실들을 자동으로 추출한다. 
그 중에서도 중요도가 일정 점수 이상의 높은 신뢰도의 대화는 질문과 답변 일부를 semantic 에 저장한다. working(단기)메모리가 일정 횟수 씌여진 상태에서 그래프 노드를 통과하는 시점에서 메모리 저장소를 통합하고 최적화를 수행한다. 
만약에 대화 중에 "이전에 유사암에 대해 어떤 질문 했었죠?" 라는 질문에는 agent 가 episodic 에서 세션 ID 와 Thread ID 에 부합하는 최근 내용 순으로 질문과 응답쌍을 리턴할 것이고, "제 성향에 맞는 특약 추천해주세요" 라는 질문에는 semantic 에서  가격 효율적 상품을 중시한다는 내용을 회고하게 된다. 
"갑상선암 진단 받으면 청구절차가 어떻게 이루어지나요?" 라는 질문에는 절차로서 세부 보장항목 단계적 확인 - 기본 보장내용 확인 - 세부 치료비 항목 확인 - 특약 및 조건 검색... 등과 같은 내용이 검색되어 보여지게 된다.

<그림 3. 가젤 Agentic 메모리관리 생명주기>

Semantic 메모리의 경우는 아래와 같이 저장된다.

store_semantic_memory{
     "facts": "상세한 보장내역을 원함",
    " domain": "insurance",
    " category": "detailed_analysis_preference",
    " related_concepts": ["detailed_analysis_preference", "thorough_analysis", "coverage_focus"],
    " confidence": 0.85,
    " source":  f"{customer_id} 상담패턴 학습,
    " timestamp": datetime.now().isoformat()
}

Langmem 에서는 create_react_agent 호출을 OpenAI 나 Anthropic LLM 을 사용하여 기존 메모리 변경, 삭제, 확장 검색을 수행하고 지속저장소를 통한 production 용으로 PostgreStore API connection 을 제공한다. Open Weight LLM 으로 적용하기에 적합하지 않다.

Gazel Agentic Memory 는 오픈소스 PostgreSQL with pgvector extension 을 on-premise 에 설치하여 Gazel 자체의 PgvectorStore class 와 HierarchicalMemoryManager 추가로 추적성, 확장성, 버그 컨트롤에 견고한 확장 환경과 지속성을 보장하도록 하였다.

AISummit 전시장 데모 시연에서는 아래 그림 4와 같이, GAZEL 전문 보험 중개 지원 Intelligent AI 데모라는 내용으로 4개의 tab 으로 시연 하였다.


<그림 4. 가젤 보험 중개 지원 데모>


첫번째, 두번째  tab 에서는 고객의 "삼성화재의 갑상선암 보장에 대하여 알려줘" 라는 질문에 대해 LangGraph 워크플로우가 실시간으로 진행되어 옆에 해당하는 Agent Tool 들이 수행되는 내용이 실시간으로 표시되었고 질문에 대해 갑상선암의 보장명, 지급사유, 지급금액등의 내용이  표로 정리되어 실시간 출력되어 보여진다.
 
세번째 tab 에서는 메모리학습이라는 내용으로 3명의 고객 각각의 일련의 질문들이 어떻게 가젤 Agentic 메모리 관리를 통해 4가지 유형의 메모리에 저장되는지를 LangGraph 워크플로우 와 Agentic Tool 처리 패널들을 통해 확인하였다. 각각의 질문이 데모시작과 함께  LangGraph Workflow 의 memory_update 노드를 거치면서 오른쪽 패널에는 각 노드에 해당하는 Agentic Memory Agent Tool Call 실행 상황이 실시간 표시되도록 데모를 진행하였다. 이 과정에서 각각의 질문에 대응하는 Episodic, Semantic, Procedural 메모리로 실시간 저장되는 현황을 E-E-S-P 와 같이 눈으로 확인할 수 있게 시연되었다.

네번째 tab 에서는 문맥기반 메모리 검색이라는 내용으로, 3명의 고객이 각각 질문한 유형들이 Tab 3 를 거치면서 Episodic, Semantic, Procedural 메모리로 저장되어 있는 상태에서 새로운 질문 예를 들면, "이전에 제가 어떤 질문 했었죠?", 혹은 "제 성향에 맞는 특약 추천해주세요" 라는 질문에 대해서 LangGraph 에 워크플로우가 실시간 진행되면서 오른쪽 패널에 해당 Agentic memory tool call 수행 표시와 함께 관련 메모리 유형(E, P, S) 과 동시에 agent 메모리가 실시간 검색해 낸 내용이 표시되는 순서로 데모가 진행되었다.





2025년 11월 10일 월요일

기업 폐쇄망에서 보험중개 지원 Agentic RAG 구현하기 – part 1

AISummit Seoul 2024에서 "기업 폐쇄망에서의 Private LLM 기반 Agentic RAG 구축"이라는 내용으로 발표한 이후로 오늘 AISummit Seoul 2025 전시장에 "기업 폐쇄망에서의 보험 중개 지원 Agentic RAG" 를 선보이게 되었다. 올해 전시는 폐쇄망에서 Agentic RAG 를 실증적으로 보험 중개 상담 지원 분야에 적용해 본 전시였고 시행착오를 통해 상용화에 적용할 수 있는 기반 프레임워크를 구현할 수 있었던 소중한 경험이었다고 생각한다. 구현하면서 몸소 배웠던 몇가지 점들을 공유한다.



<그림 1. 전문보험중개지원 Agentic RAG 데모>

폐쇄망의 Agentic RAG


기업 폐쇄망에서 라는 문구에서 알 수 있는 바와 같이, 보안을 위해 On-premise 나 self-hosted 의 환경에서 기업내부 지식기반을 중심으로 Open Weight LLM 으로 챗GPT 가 제공하는 것과 같은 기능을 RAG 기술 기반으로 적용하는 것을 뜻한다. 
OpenAI와 같은 외부 API 를 사용하지 않고, 모델, 워크플로우 프레임워트, 벡터 데이터베이스, agent 관리, agentic 메모리 관리, 가드레일 및 평가 등을 오픈소스로 구현하는 것을 뜻한다.

기술 흐름과 방향성


2025년은 하루가 멀다하고 쏟아지는 LLM 들과 agent 관련 내용이었다. 특히 딥시크 모멘트 이후 Open Weight LLM 이 챗GPT 에 버금가는 성능으로 진화할 수 있다는 가능성을 보여줬고 논문들로부터 시작하여 매개변수가 상대적으로 작은 LLM 으로 test-time computing 같은 post-training 으로 특정 산업분야에서 챗GPT-4o 에 준하는 성능을 보여주는 가능성이 부상하였다. 
아울러 우리도 그동안 50개가 넘는 모델들을 파인튜닝해 적용해 왔지만, 작금의 LLM 발전 추이를 볼 떄, 좀 더 산업 도메인에 특화된 맞춤형 agent 플랫폼이라는 방향으로 추진해야 하지 않을까 하는 생각을 하게되었다.

폐쇄망에서 오픈소스를 통해 챗GPT Agent 같은 기능을 구현할 때 직면하는 도전


오픈소스를 통해 Agentic RAG 를 기능을 구현할 때 처음 드는 생각들을 간추려보면 대략 아래과 같지 않을까 생각한다.

1. 멀티모달 문서 parsing: 즉 문자, 표, 챠트, 이미지 등이 있는 문서들을 corpus 로 도입해서 LLM 을 통해 원하는 출력을 얻으려면 어떻게 해야 하나?

2. LLM 성능: 어느 정도의 LLM 을 채택해야 tool 선택과 호출이 의도대로 될까?

3. Agent Tool 구현: 에이전트 지원을 위한 Tool 등록, 선택 및 수행등을 관리하는 기능은 어떻게 구현할 것인가?

4. 파이프라인 오케스트레이션 프레임워크: stateful workflow 로서 파이프라인을 조율하는 오픈소스 프레임워크는 무엇을 채택할 것인가? 그리고 충분한 기능이 지원되나?

5. Smarter Prompt Parser: 다양한 LLM 의 서로 다른 프롬프트 템플리트의 형식을 포용할 수 있는 smarter & robust parser 구현은 어떻게 할까?

6. Agentic Memory: Agentic 을 사용할 때 한정된 메모리상에서 대화가 거듭될 수록 증가하는 프롬트로 인한 메모리 제약을 어떻게 해결할까?

7. Agentic Memory: 메모리의 중요성이 눈에 띄게 증가하는 추세를 감안할 때, self-improving system 혹은 continuous learning system 이라는 측면에서의 agentic memory 관리는 어떻게 구현하나?

8. Tool Binding Wrapper:OpenAI 형식의 시스템 메세지나 schema 를 간편하게 tool 에 주입할 수 있도록 오픈소스로 구축하면서 OpenAI 와의 호환성을 보장할 순 없을까?

9. 성능평가: Agentic RAG 의 성능평가는 어떻게 할 것인가?

10. 가드레일: 가드레일을 어떻게 보장할 것인가?

봉착하는 현실에서의 상황


1. 멀티모달 문서를 파싱(parsing)하는 솔루션들은 해외나 국내조차도 구독형 등의 유료 모델로 바뀌었다.

2.Agent 지원 LLM 은 지금 이 순간에도 수 없이 나오고 있다. 이제는 그 시점에서 테스트해보고 가장 좋은 걸 채택하면 된다. 예전에는 질의응답 합성데이터를 만들어서 파인튜닝하고 했는데 최신모델들은 신기할 정도로 파인튜닝을 안해도 프롬프트 조정만으로도 예전 성능을 능가한다.

3,4. 처음에는 LangGraph 같은 오픈소스 프레임워크로 어디까지 해결 할 수 있을지 궁금해할 수 있다. LangGraph 도 수익모델을 생각하니 OpenAI 를 중심으로 서서히 유료화로 진행하는것을 탓할 수는 없다.

  LangChain 이 toollist 를 system/context 프롬프트에 주입하고 Open Weight Model 이 " I want to use tool X" 혹은 "Call: Tool_name" 같은 문구를 보고 실행되기를 기대하지만, 
전적으로 Open Weight LLM 의 instruction 처리, tool use 사용 능력에 의존해서 일관성이 부족하다는 것을 느끼는데 오랜 시간이 걸리지 않는다. 
LangChain 기반에 Open Weight LLM 를 사용한 구조화된 function call 이 갖춰서 있지 않아서 tool 을 요구하면 LangChain 이 추측하는데 있어 상당한 시행착오가 수반된다. 
따라서 ChatHuggingFacePipeline 이 Open Weight LLM 에 tool 을 정의하고 등록하는 것이 필요하게된다.

5. Open Weight LLM 채택의 장점은, 그 시점의 SOTA 모델을 도입할 수 있는 장점이 있다. 
그동안은 LlamaMixtral 등등의 LLM 프롬프트 형식 차이로 인해 LLM 이 변경될 때 마다 LLM에 주입되는 프롬프트에 주의해야했다. 
Univeral Parser 로서 LLM 에 정밀한 프롬프트로 주입될 수 있도록 보장하는 Smarter Parser 가 요구된다.

6,7. Agent 사용 시 메모리 관리는 점점 그 중요성이 더해지고 있는 중요한 주제이다. 임시 메모리상에 쌓이는 내용을 단기와 장기 메모리에 적절히 저장하고 대화 문맥에 맞게 실시간 회상하는 기능이 대두된다. 논문에서 인간의 뇌의 기억저장을 본 뜬 계층형 메모리 관리로 인간의 시스템 1과 시스템 2와 같은 관리를 위해 agentic memory 시스템을 통한 계층형 메모리 관리라는 부분을 고려할 계기가 되었다.

8. LangChain 의 agent RAG 예제에서 다루어지는 create_react_agent 함수는 Open Weight LLM 에서 수행되지 않았다. 여기서 다루어지는 LangChain 의 bind_tool 이나 with_structured_output 기능을 OpenAI 사용하는 것과 같이 흉내내기위해 ChatHuggingFaceToolWrapper 와 같은 tool binding wrapper 설계를 생각해보았다.

9. 일반적인 RAG 의 기본적인 평가도구는 Ragas, DeepEval, TruLens, LangSmith 등이 거론 되지만, 아직 Agent 관련 공인된 Leaderboard 를 정립하려고 모색하는 단계여서 추이를 지켜보는 것이 낳을 것 같다.

10. 가드레일을 어느 정도로 갖추어 하는지가 상용화 요건에따라 다르겠지만, 일정 수준의 가드레일을 갖추고 우리같은 여건에서 어떻게 해야하는지 추이를 보고 있는 중이다.

**Part 2 에서 이어집니다.






2024년 12월 29일 일요일

기업폐쇄망에서의 Private LLM 기반 Agentic RAG 구축 - 2편

 Agentic Workflow

1편에서 전통적 RAG 의 제한적 요소들에 대해 언급하였는데 이 중 의사결정, 동적 프롬프트 조정 그리고 외부 tool 접근 등은 AI Agent 기술의 등장과 더불어 보다 진보된 RAG 로서  그 면모를 새롭게 갖추게 되었다. 우선 그럼 Agent 라는 것이 무엇인가를 살펴볼 필요가 있다. 아래 그림 5 는 Agentic workflow 가 이전 zero shot 방식의 workflow 와 어떻게 다른지를 직관적으로 설명한다. 
아래 그림 왼쪽의 워크플로우는 어떤 주제에 대한 에세이를 작성하는데 있어서 시작부터 끝까지 한번에 쉼없이 작성하는 워크플로우를 나타낸다. 전통적 RAG 의 워크플로우라고 할 수 있겠다. 반면에 오른쪽 Agentic workflow 는 우리 사람들이 통상 하듯이 주제에 대해 에세이 개요를 먼저 생각해보고 초안을 작성하고 생각한 다음, 변경이나 조사가 필요한 부분을 조사한 후 변경하는 형태의 생각과 수정을 반복하는 과정을 거친다. Think Fast and Slow 의 저자 다니얼 커너먼은 이를 system 1 과 system 2 로 설명한다. 즉 우리가 10X 12 와 같은 곱셈은 직관적으로 120 이라고 답하는 system1 과 같은 사고라면, 240X126 과 같은 곱셈은 가만 있어 보자 하고 생각하면서 좀 더 시간과 두뇌를 더 쓰면서 답변하는 system 2 라는 사고 과정이라는 것이다. 이렇게 LLM 이 좀 더 숙고하고 변경을 거치는 워크플로우는 따라서 AGI 에 가까워지는 보다 고도화된 지능을 갖는 과정이라고도 할 수 있다. 


<그림 5. Agentic Workflow>

Agentic RAG

그러면 Agentic RAG 는 무엇일까? 기존 RAG 기능에 더해서 아래 그림 6과 같은 4가지 예로 보여지는 기능을 수행할 수 있는 RAG 를 뜻한다. 첫째, 기존 RAG 가 질문에 의미론적으로 유사한 문맥에 근거한 답변만을 제공하는 기능에서 제공하지 못했던 요약이나 비교, 계획등의 기능을 실현하는 것
둘째, 보다 중의적이고 복잡한 질문을 agent 의 회고(reflection)를 통하여 문장을 분석하고 보다 지능적인 답변을 제고하는 부분
셋째, 지식기반중에 정형 SQL 데이터를 보유한 경우에 자연어 질문으로부터 정형데이터의 접근 필요성을 감지하고 자연어 질문을 SQL 형태의 코드로 변환하여 SQL 데이터베이스를 검색하여 답변을 제공
넷째, function calling 기능이 부여된 LLM 을 통하여 web search 같은 외부 tool 을 통한 외부정보 검색 등을 제공하는 것을 의미한다.


<그림 6. Agentic RAG 예>

Spotify 에서 상위 5위까지의 유행곡

아래 그림 7은 이러한 tool을 통한 외부 정보 검색의 예를 보여준다. 1)에서 들어온 질문이 외부 tool 을 사용하여 답변해야 하는지가 결정되면 Agent 가 지원되는 LLM 을 통하여 2)와 같이 LLM 이  어떤 외부 tool을 자동선택(이 경우 web search tool) 하여 웹접근용 코드를 작성(실행)하고 3)번과 같이 웹검색에 전달될 tool 의 매개변수를 예측하여, 4)에서 5개 음악 곡의 곡명과 가수명 랭킹을 추출하여 5)번에 agent LLM 을 거쳐 6)번 LLM 을 통한 응답하는 흐름이다. 


<그림 7.  tool 을 통한 외부 검색 예>

예: tool 을 통한 외부 정보 검색 

아래 그림 8은 Llama 3.1 70B 함수 호출 기능을 사용하여 "스포티파이에서 상위5위까지의 유행 곡을 알려줘?" 라는 질문에 대해 내부적으로 함수 호출의 형식과 매개변수표시등의 프롬프트 방식의 예를 보여준다.
Llama 3.1 은 웹서치 기능을 제공하는 brave_search 함수, 계산 기능을 제공하는 wolfram_alpha 와 파이선 코딩을 지원하는 ipython 함수를 내장하고 있다. 이번 예 에서는 brave_search 와 ipython 함수가 선택되어 사용되었고 내부 매개변수는 5개 곡 곡명, 가수명 랭킹 등이 내부적으로 LLM 함수 호출에서 자동 예측되었다.


<그림 8. Llama 3.1 70B 함수 호출 예>

Behind the Scene

아래 그림 9는 그림 8 의 함수호출에서 내부적으로 수행되는 코드를 보여준다. 윗쪽 화살표에 보여지는 시스템 프롬프트에 담긴 내용은 ipython 환경이고 brave_search 라는 웹검색 도구와, wolfram_alpha 라는 계산기 도구들이 있으며 tool instruction 은 언제나 파이선 코드를 실행하고 실시간 정보를 탐색 시 만약 관련 함수가 있으면 사용하고 없으면 brave_search 을 사용하도록 되어있다. 아래 화살표는 agent 가 생성한 내부적으로 수행된 코드를 보여준다. spotify 를 import 하고 client id 를 가지고 spotify 계정의 토큰을 획득하며 items['name'] 과 items['artists'] 을 limit=5 로 국한하여 검색함을 알 수 있다. 


<그림 9. Behind the scene>

Vector Index vs Summary Index

아래 그림 10은 전통적 RAG 의 vector index 와 더불어 요약을 담은 summary index 를 통하여 전통적 RAG에서 구현할 수 없었던 문서 요약을 수행하는 예를 보여준다. 질문이 주어졌을 때 Router 를 통하여 전통적 질의 응답에 관련된 경우인지 요약을 필요로 하는 경우인지가 판별되고 요약이라고 판별될 경우에 오른쪽 경로를 타고 Summary 담당 query engine 이 summary index 를 통하여 요약을 검색한다.  이러한 과정에서 LLM Agent 가  필요한 함수와 연관되는 매개변수 역시 자동으로 유추한다.


<그림 10. Vector vs Summary Index>

Retrieving using Vector & Summary Tool

아래 그림 11은 이러한 vector index 와 summary index 라는 2개의 인덱스가 있는 상태에서 질문이 들어왔을 때 summary tool 과 vector tool 중에 어떠한 것을 사용할지를 LLM 의 agent 기능이 어떤 도구와 매개변수를 명시하여 사용할지를 지능적으로 유추하는 예를 보여준다. summary_tool 은 query_engine 으로 summary_query_engine 을 사용할 것을 함수에 명시적으로 선언하고 description 에 "llmissue_spri.pdf 와 관련된 요약에 대한 질문에 사용됨." 이라는 placeholder 를 선언해준다. agent tool 에서 함수는 전통적 파이선 함수와 다른 점은 LLM 이 이 description placeholder 내용을 함수 수행에 활용한다는 점이 차이점이다. 
아래 두번째 주피터 노트북 셀에서 ReActAgent.from_tools 라는 REACT agent 구성을 선언하였다. tool 의 매개변수로 [vector_tool, summary_tool], lllm 을 명시하였고 결국 REACT agent 가 이 둘 중에 질문의 내용을 보고 어떤 함수와 매개변수를 명시할지를 결정한다. 
아래에 response=agent.chat("ChatGPT 기술의 차이점은 요약은?") 이라는 질문에대해 하단에 Thought, Action, Action Input, Observation, Thought 로 진행되는 절차로 reasoning 과 action 이 반복 됨을 확인할 수 있고 마지막으로 Answer 를 출력하는 것을 보여준다.


<그림 11. Vector & Summary Tool 을 이용한 검색>

ReACT: Reasoning + Acting with LLM

앞에서 설명한 ReAct Agent 호출은 아래 그림 12 에 표시된 ReAcT 논문 에 기반한 이론을 기초로한다. 2023년 3월에 구글 브레인과 프린스턴대학교에서 발표된 논문을 요약하면, 이전까지는 Chain of thought 로 대별되는 Reason only 와,  WebGPT 와 같은 행동을 취한 다음에 결과를 관측하는 Act only 가 별도로 분리되어 있었는데, 논문에 의하면 이러한 Act-Observation 같은 Reason only 프롬프트 방식이나, Act-Observation 같은 Act only 프롬프트 방식으로 풀지 못하는 문제를 앞의 그림 11에서 나타난 Thought-Act-Observation 이 순환되는 형태의 프롬프트가 응답을 도출해내며 보다 높은 지능을 보여 준다는 것이다. 이를 위하여 이러한 Thought-Act-Observation 의 절차에 충실한 few-shot 형태의 프롬프트 template를 통하거나 langchain 같은 프레임워크에서는 이러한 few-shot 형태에서 보다 표준화된 template 를 사용하여 ReAcT Agent 를 구현한다.


<그림 12. ReAcT>

Text to SQL with ReAcT Agent 

Agent 를 사용하여 기존 RAG 에서 수행할 수 없었던 SQL 데이터베이스 정보 검색 수행을 할 수 있다. 아래 그림 13은 11개의 테이블 스키마를 보유한 SQLITE Tutorial 의 음원 판매 정보 관련 데이터베이스들을 예를 들어 설명한다.


<그림 13. 샘플 SQL DB>

Agentic RAG Flow: 앨범판매에 어느 국가가 가장 많은 돈을 지출?

아래 그림 14는 "국가별 총 판매액을 나열하세요. 어느 국가의 고객이 가장 많은 돈을 지불했나요?" 라는 자연어 형식의 질문에 대하여 ReAcT 를 agent 를 활용하여 SQL Agent 가 선택되고 flow 가 진행되는 것을 보여준다. 
1)에서 주어진 질문이 SQL Agent 를 사용해하는가 를 판별하고 
2)에서 Agent 를 통해 테이블 schema 를 보고 관련 query 처리 여부르 판단하는 SQL Query 를 작성하도록 생성하며, 만약 SQL 처리가 맞다면 
3)에서 schema 들로부터 agent 가 판별한 'customer' 와 'invoice' 라는 매개변수를 예측한다.
4)에서 질문에 답을 하기위해 'customer' 와 'invoice'를 join 한 SQL 문을 작성한다. 결코 쉽지 않는 SQL 구문이다. 
5)에서 query 를 실행하여 국가별 전체 매출을 순서대로 출력하여 6) 과 7)을 거쳐 응답을 리턴한다.


<그림 14. text-to-SQL 예>

ReAcT Agent & ChatOllama

아래 그림 15는  llama3.1 70B 모델을 Ollama 를 사용하여 chat 형태로 구현한 예로 첫번째 cell 에서는 Langchain 프레임워크를 사용한 AgentType.ZERO_SHOT_REACT_DESCRIPTION 을 사용하였다. 그 아래로 Entering new SQL Agent Executor chain... 이라는 SQL Agent 를 자동 선택한 내용이 보이고 이어서 Thought-Action-Action Input-Observation 등이 계속 반복되며 진행되어 sql_db_list_tables 를 검색하기 위한 SQL 코드가 작성되고, 그 다음 sql_db_schema를 검색하는 진행절차가 보인다. 
verbose=True 로 설정하여 주피터노트북에 표시되는 내부 실행 내용은 무척 길어 Action 부분만 캡쳐하여 표시하였다. 
맨 마지막에 Final Answer 로 미국의 고객이 가장 많은 돈을 지불했으며, 총 판매액은 523.86 입니다 라는 응답을 보여준다. 내부적으로 생성되는 SQL 코드는 쉽지 않은 자연어 질문에 대해 reason-act-observation 이라는 과정을 통해 정답을 찾기위한 agent 의 자체 학습, 목표 설정 그리고 동적 프롬프트 조정 능력을 보여준다. 
정형데이터 검색에 초기의 접근법을 보여준 것으로 보이고 지능적인 답변을 위해 소요되는 컴퓨팅 파워와 시간등을 감안하면 실제 정형데이터 검색에 얼마나 프로덕션 수준에 접근할 수 있을지 그 귀추가 주목된다. 아울러 생성된 SQL 코드의 실시간 검증이라는 부분이 어떻게 해결될지도 생각해볼 부분이다.


<그림 15. ChatOllama 로 구현한 Text-to-SQL>

결언

눈부시게 발전하는 OpenAI 와 같은 폐쇄형의 선두 기술들을 오픈소스 프레임워크등을 통해 폐쇄형의 온프레미스 환경에서 구현하는 것은 여러면에서 도전적이다. 오픈소스 진영이 폐쇄형 선두주자들과 어깨를 나란히 하는 시점이 올것이라는 믿음으로 이제 Agent 라는 새로운 지평의 초입에 서있다. 
전통적 RAG 이 시간과 정확성에 중점을 두고 진화해 왔다면, 이제 Agentic RAG 이라는 명제는 각 산업의 보다 다양한 내부 업무흐름에 보다 고도화된 지능에 기반한 검색 요구를 대응하기위한 하나의 방안일 것이라는 것에는 이견이 없을 것 같다. 
ChatGPT 가 2023년 6월 function calling 이라는 기능을 발표하면서 촉발된 이 agent 기능이 RAG 에 접목되는 매우 초보적 수준의 개념 증명 같은 내용을 공유한 것 같다. 그러나 확실한 것은, agent 는 결국 모든 산업 프로세스에 중간 broker 들을 없애고 agent 가 직접 추론 유추해서 적절한 기능을 자동 수행한다는 점에서 그 성장성은 매우 크고 Agentic RAG 또한 많은 관심을 받을것으로 예상한다.

기업폐쇄망에서의 Private LLM 기반 Agentic RAG 구축 - 1편

AI Agent 트렌드 부상

 지난 12월11일 코엑스 그랜드볼륨에서 개최된 AI Summit Seoul 2024 에서 "기업폐쇄망에서의 Private LLM 기반 Agentic RAG 구축"이라는 주제로 발표를 하였다. 올해로 7년째 개최되는 AI Summit Seoul 2024는 12월10일~11일 양일간 개최된 유료 행사로 100만원에 육박하는 가격에도 매회 1,500명 참석자가 행사 조기에 등록이 마감되는 국내 최대 AI conference 중 하나다.  예상했던 것과 같이 AI Agent 가 트렌드로 부상함을 느낄 수 있었다.  발표했던 내용을 간추려 정리해본다.



Closed-source vs Open-weight models

아래 그림 1과 같이 2022년4월부터 2024년 7월까지 GPT, Claude, Gemini 등의 폐쇄형 소스 기반 모델의 성능향상(MMLU 5-shot)의 증가가 2023년 10월 기점으로 87% ~89%의 완만한 증가세를 보이는 반면 같은 기간 Qwen 1.5 72B, Llama 3.1 70B, 405B 등의 Open-weighted 모델의 급속하게 추격하고 있음을 확인할 수 있다. 무엇보다도 올해 Meta 사의 Llama 3.1 405B 을 개방형 weight 로 발표함으로써 OpenAI 사의 GPT-4o 와 비견되는 성능을 온프레미스 환경에서 실행할 수 있는 기회가 주어진 점이 오픈소스 진영에 의미하는 함의가 크다고 하겠다.

2023년 3월 스탠포드대학에서 GPT-3 를 이용한 self-instruct 생성으로 Llama-7B 모델을 파인튜닝한 Alpaca 모델 공개로부터 영감을 받아 시작된 Open-weight 모델을 통한 Private LLM 개발 여정은 그동안 Polyglot, Llama 2, Falcon, Mixtral 8x7B, Llama 3 8B, Llama 3.1 8B, 70B 그리고 405B 까지 나 역시 아래 그림 1과 같은 궤적을 쫓아왔고 어느새 50개가 넘는 파인튜닝 모델을 보유하게 되었다는 점에서 감개가 무량했다. 이 과정에서 RAG(검색증강생성)를 위한 임베딩 및 LLM 모델 파인튜닝용으로 사용될 instruction 생성 데이터세트에 사용되는 GPT-4 의 성능을 대치할 모델로 Llama 3.1 405B 모델이 Open-weight 모델로 발표되어 매우 흥분되었었고 기업내부 폐쇄형 환경에서 외부 API 접속 없이 RAG 를 완성할 수 있을 것 같다는 나의 가설이 현실화될 수 있다는 생각에 기뻤다. 2023년 3월부터 모든 것이 가설과 실행, 에러, 수정 그리고 확인의 쉽지 않은 과정이고 무모할 수 있는 도전이었는데 어쨌든 현재는 여기까지 왔다.


<그림 1. Closed-source vs Open-weight models>

Llama 3.3 70B beats GPT-4o, Llama 3.1 405B

행사에서 발표하기 5일 전인 12월7일 마침 Meta 사의 Llama 3.3 70B 의 발표가 있었다. 3.1의 405B의 성능을 3.3 버전에서 70B으로 비슷한 성능을 제공한다는 것이다. 그간 405B 을 사용하기위해 4비트 양자화 버전인 경우에도 최소 H100 4대 정도의 환경을 보유해야 하는 제약을 3.3 버전의 70B 모델은 4비트 양자화의 경우 H100 1대에서 운영(약 42GB 메모리 필요)할 수 있게 해준다는 것을 의미한다. 이는 합성데이터 생성은 물론, 앞으로 급부상할 AI Agent 에서 의미하는 바가 크다. Meta 의 허깅페이스 모델 카드에서 언급한 바와 같이, Llama 3.1 8B 로는 AI Agent 를 수행할 tool calling 에서 제약이 따라서 70B, 405B 을 사용하기를 추천하고 있다. 
 아래 그림 2는 Llama 3.3 70B 과 GPT-4o 등과의 성능 비교를 보여주고 있다. 초록색 네모칸과 같이 Multilingual MGSM 의 경우, Llama 3.3 70B 이 GPT-4o 보다 뛰어난 성능을 보여주어 한글 데이터처리에 이점이 있을 것으로 보이고, 빨간색 네모칸은 가격 면에서 GPT-4o 를 사용하는 비용의 1/25 의 가격으로 사용이 가능함을 보여주고 있다.

<그림 2. Llama 3.3 70B beats GPT-4o>

Basic RAG Flow

아래 그림 3은 일반적인 RAG(검색증강생성) 흐름을 보여준다. GPT-4 에다가 국내 기업의 데이터와 관련된 질문을 하면 수조개의 데이터로 사전학습된 내용을 기반으로 해서 응답을 하므로 웹상에 존재하지 않는 기업내부 데이터에 맞는 데이터보다는 그럴듯한 응답(할루시네이션)을 제공할 확률이 크다. 이러한 LLM 의 국내 비정형 데이터 검색이라는 측면에서 2024년 한해 RAG를 검토한 기업들이 많았고 결과도 어느정도 만족할 성과들이 있었다. 먼저 기업내부의 비정형 문서들(HWP, MSDOC, PDF 등)을 문자열 형태로 읽혀들인다. 
이때 2)번과 같이 문자열로 변환된 전체 문서를 일정크기(chunk)의 문서로 분할한다. 다음 1) 번과 같이, 임베딩 모델을 사용하여 분할된 문서를 3)번과 같이 벡터로 변환한다. 예를 들어, 700 token 크기로 문서를 분할했다면, 700 토큰에 대응하는 벡터 내용들이 쭉 4)번의 vector DB 에 저장된다. 
5)번에 dataset 은 2)번의 분할된 문서를 '참조' 문서로 하여 질문과 답변을 생성하는 합성데이터를 의미한다. 이렇게 만들어진 합성데이터는 1)번의 임베딩 모델과 6)번의 Private LLM 모두에 파인튜닝하여 base model 로 사용된 Llama 3.1 8B 이 학습하지못했던 기업데이터에 few-shot 학습할 기회를 제공한다. 이렇게 준비된 상황에서 질문이 들어오면 7)번과 같이 질문도 역시 임베딩 모델로 벡터화하여 8)번과 같이, 질문 벡터와 vector DB에 저장된 벡터들과 1:1 유사도검색을 통하여 9)번의 사전에 정의된 Top-K 즉 유사도 순서로 랭킹 몇 위까지 표시하는 것에 따라 벡터DB 로 부터 유사 문서를 추출한다. 
만약 top-K 가 2라면 2개의 의미(semantic)가 유사한 문장이 검색되어 온다. 이 2개의 참조 문헌에 9)번과 같이, Private LLM (예를 들면, 파인튜닝된 Llama 3.1 8B)이 프롬프트(질문내용)를 던져 응답을 생성한다.


<그림 3. Basic RAG Flow>

Challenges: Building RAG in Closed Network System

발표주제에서 의미하는 바와 같이, 기업 폐쇄망에서의 Agentic RAG 구축은  몇가지 도전적 요소를 내포한다. 첫째, 당연한 이야기지만 기업폐쇄망에서의 구현은 외부 API 와의 연결이 보장되지 않는 곳이다. 눈부신 속도로 발표되는 최신 SOTA 모델 적용에도 허깅페이스와 같은 모델 저장소를 사용하지 못한다. 이를 위해 우리는 치타라는 LLMOPs Model Repository 에 기업용 파인튜닝된 모델 set 를 저장하고 이로부터 Git/LFS 같은 형태로 허깅페이스와 같은 모델 로드를 시도한다. 
둘째, 임베딩모델과 LLM 모두를 대상으로 기업내부 정보를 파인튜닝하는데 필요한 instruction dataset 을 생성하는데 외부 GPT-4o 를 대체할 성능의 합성데이터 생성용 로컬 LLM 이 준비되어야 한다.
셋째, RAG 를 구성하는 LLM, Vector DB, 트랜스포머, bitsandbytes 와 같은 양자화, Langchain, LlamaIndex 와 같은 LLM 응용 프레임워크 등 가용 기술요소가 파이선이나 파이토치와 같은 환경상의 최적화가 필요하다. 마지막으로, 이 들간에 이음새없는 통합이 필요하다. 오픈소스를 통한 개발에는 버전간의 충돌로 인한 통합에 많은 정성이 들어가는 것이 사실이다.

기업내부망의 LLM/RAG를 위한 효과적인 파이프라인을 제공하는 LLMOps

이러한 폐쇄망에서의 도전적인 요소들에 대응하고자 LLM과 RAG 개발/운영 측면에서도 LLMOPs 로의 전환이 불가피하게 되었다. '치타'는 그간 고객들이 요구하는 폐쇄형 환경에서 많은 참조사례를 확보한 바 있는데 이를 기반으로 LLMOps 로의 확장이 신속하게 이루어졌다. 아래 그림 4는 '치타'의 LLMOps 라이프사이클 파이프라인 모습을 나타낸다. 특히 1) Data Management 기능에서 데이터셋관리와 어노테이션 관리 부분이 강화되었고, 2)번 모델 개발/훈련에서 직관적인 워크플로우 기능과 워크스페이스 기능이 3)번 모델 관리에서는 임베딩 모델과 LLM 모델 저장소가 특히 4) 모델 서빙에서는 엔디비아에 특화된 Tritron Server 추론 방식을 채택하였고, 5) 모니터링에서는 모델 성능 모니터링과 더불어 오토스케일링 기능을 제공하여 서빙에서 부하가 증가할 경우, 자동적으로 GPU 자원을 확장하는 기능을 제공한다.

<그림 4. 기업내부망의 LLMOps>

Limitations of Traditional RAG

RAG는 분명히 내부데이터 보안 문제 없이 AI 검색을 제공하는데 역할이 있다. 하지만 아래와 같은 이유들로 제한 사항이 있는 것 역시 사실이다, 
첫째, 질문에대한  의미에 유사한 정보를 제공하는 단방향의 솔루션이다. 즉 보다 검색해보고 싶은 탐색적 능력이 부족하다.
둘째, LLM 이 사용되고 있지만, 자체 학습, 목표 설정과 같은 의사결정이 결여되어 있다.
셋째, 프롬프트는 어느정도 주어진 프롬프트를 사용한다. 질문과 답변이 목적이기 때문에 그렇다. 새로운 정보에 적응해서 동적으로 프롬프트를 조정해서 LLM이 보유한 보다 지성적인 동적 프롬프트 조정능력을 활용할 순 없을까?
넷째, 웹검색이나 SW API 같은 외부 tool 에 접근이 불가하다.

- 기업폐쇄망에서의 Private LLM 기반 Agentic RAG 구축 - 2편에서 계속됩니다.





2023년 7월 31일 월요일

영문 오픈소스 LLM에 한글 데이터세트로 파인튜닝하기

 한글 경량화 Private LLM 개발

메타에서 2월에 Llama 거대 언어모델을 발표하였다. 커뮤니티의 연구자들은 그간 내부구조와 코드를 공개할 것으로 기대했던 OpenAI의 챗GPT의 다른 행보에 따라 거대언어모델에 관심이 높던 상태에서 Llama 언어모델 공개로 관심을 끌었다. 곧이어 스탠포드에서 이 언어모델에 instruction 세트들을 OpenAI 모델을 사용하여 instruction 과 ouput 형태의 데이터세트들로 파인튜닝한 instruction tuned LLM 을 선보임에 따라 연구 커뮤니티들은 후속되는 챗GPT를 모방할 수 있는 LLM 개발 가능성에 영감을 얻고 Llama 를 기반 모델로 한 후속 오픈소스 LLM 모델이 속속 발표되는 계기가 되었다. 
본 프로젝트는 이러한 영문 오픈소스 LLM을 한글에 적용하는데 필요한 모델 개발을 목적으로 한다. 한글 챗GPT(GPT-3.5)와 같으면서 상용화가 가능하고 비교적 저 사양의 추론 환경에서 사용할 수 있도록 훈련 가능 모델 파라미터와 메모리 공간을 절약할 수 있는 QLoRA 와 4bit quantization 기술이 적용된 모델 개발을 목적으로 한다. 
이러한 한글 경량화 LLM 은 OpenAI API 를 통한 외부 접촉을 통한 사용에 민감한 국내 기업들이 개별(Private) LLM 을 통하여 기업 내부 지식기반을 대상으로 한 챗GPT 운영의 필요성 대두에 기인한다.  Alpaca 7B을 A100 8대로 8시간 훈련하여 OpenAI 의 챗GPT에 어느정도 근접한 성능을 보인다는 경로를 따라 영문 instruction 데이터세트 대신 한글 데이터세트를 이용한 파인튜닝을 시도해보고자 한다.

기반 모델
오픈소스 LLM 중에서 상용라이선스가 허용되며 한글데이터세트로 사전학습된 모델을 찾기가 용이하지 않았다. 아래 그림1의 허깅페이스 리더보드에 나타난 EleutherAI 사가 개발한 polygot-ko-12.8b 모델을 사용하였다. 이 모델은 Apache 2.0 라이선스에 한국 AI 스타트업 TUNiB 사에 엄선된 863GB 의 한글 언어 데이터로 사전학습된 모델이다. 영문 기반모델의 경우, 254대의 A100 GPU로 GPT-NeoX 모델을 1670억 토큰을 301,000 스텝 훈련하였다. 자세한 내용을 여기 참조.


<그림 1. Polyglot-ko-12.8b 모델 허깅페이스 리더보드>


한글 데이터세트

파인튜닝을 시행할 한글데이터세트는 허깅페이스의 beomi/KoAlpaca-v1.1a 데이터세트를 사용한다. 데이터세트는 v1.0 에서 Alpaca 의 5만2천개의 instruction 예들을 지도기반 파인튜닝한 영문 데이터세트를 DeepL 번역기로 한글로 번역한 데이터세트를 적용한 결과가 만족스럽지 않아 네이버 지식인에 있는 내용중 원문과 답변을 재가공하고 엄선하여 21,155 행의 instruction, output, url 형식으로 아래 그림 2와 같이 구성한 데이터세트다. url 은 네이버 지식인의 원문 출처다.

<그림 2. 한글 데이터세트>

KoAlpaca v1.1a 데이터세트 로드

아래 그림3과 같이, ① 허깅페이스의 datasets 를 설치, ② load_dataset을 도입, ③데이터세트를 로드한다.

<그림 3. 데이터세트 로드>

아래 그림4와 같이, lambda 함수를 사용하여 data를 변형시킨다. data 의 ‘text’ 컬럼을 ① 과 같이 ###질문: 에 이어, ② x[‘instruction] 이 뒤따르고, 답변도 같은 형식으로 이어진 다음 ③에 endoftext 코드로 마감한다. 즉 기존 ‘instruction’: ‘output’: 형식에 질문: 과 답변: 이라는 한글을 매핑한다.

<그림 4. 데이터세변형>

아래 그림5와 같이 GPU 사용을 위한 설정을 한다. 저자는A100 GPU 4개를 사용하지만 huggingface 가 bitsandbytes 의 4bit precision 으로 모델 weight 파라미터 크기를 줄이고 QLoRA로 LLM 파인튜닝에 메모리 사용량이 줄어들어 단일 24GB GPU 에서 33B LLM 을 파인튜닝할 수 있도록 하고 있어서 24GB 이상의 메모리가 가용한 GPU가 있는 환경에서는 이론적으로 파인튜닝이 가능하다. 자세한 내용은 여기 참조. 

<그림 5. GPU 정의>

모델 로딩

아래 그림6의 ① safetensor 는 허깅페이스의 새로운 텐서 저장 형식으로 텐서를 간단하고 안전하게 그리고 pytorch 에 비해 2배 이상 빠르게 weight 를 로드할 수 있는 형식이다. Huggingface 에 있는 safetensor 모델을 model_id 로 정의한다. bitsandbytes의 4비트 quantization 을 사용하므로 ② 에서 load_in_4bits=True 는 .from_pretrained 방법으로 모델을 로드할 때 4비트 정밀도로 로드하고, bitsandbytes의 bnb_4bit_use_doule_quant=True로 중첩된 한번 더 양자화(quantization)를 사용함으로써 성능 감소 없이 보다 많은 메모리를 절약할 수 있다. ③에서 새로운 4bit 데이터형식의 weights 로 normal float 4(nf4) 데이터 형식을 사용한다. ④에서 은닉층의 행렬곱 등의 연산에 사용하는 연산 유형을 default 가 float32를 사용하는데 이를 bfloat16으로 변경함으로써 속도를 증가시킨다. ⑤에서 bnf_config로 정의하고, ⑥에서 추론시에 가능한 자원에 모델을 효과적으로 전개하도록 auto 로 정의한다. 


<그림 6. 모델 로드>

text 부분 토큰화

아래 그림7의 ① 과 같이 data의 text 부분을 위에서 정의한 tokenizer로 토큰화 한다. ② 에서 dataset.map()과 같은 유틸리티를 batch와 함께 사용하면 프로세싱 속도를 높히고 생성되는 데이터세트의 크기를 자유롭게 통제할 수 있다. 단일 샘플보다 batch 모드를 이용하면 batch에 있는 모든 샘플의 토큰화를 병렬로 처리하여 속도가 빠르다.

<그림 7. text 부분 토큰화>

아래 그림8의 ① 과 같이 data train 컬럼에 첫번째 레코드의 text 컬럼내용을 조회하면 그림4에서 mapping 한 형태로 ### 질문: 이 나타나고 ② 의 ### 답변 그리고 ③의 endoftext 가 나타난다.

<그림 8. 토큰화된 text 내용 확인>

PEFT를 이용한 low bit 훈련 준비

아래 그림9의 ① 과 같이 정의하면 경사도 연산에서 그래프의 모든 단계의 내용이 마지막 경사도 변경 연산때까지 메모리에 저장되는 것을 필요할 때만 재연산하고 이전 값들은 날림으로써 메모리 사용을 절약한다. ②와 같이 준비함으로써 PEFT 를 사용하여 모델이 LoRa 즉 모델의 각 layer에 훈련 어댑터들을 추가하도록 준비한다.
PEFT는 거대언어모델을 효과적으로 파인튜닝하기위한 다양한 기술 방법들이고 LoRa 는 그 중에 하나다. LoRa 는 low rank adaption 으로 사전학습된 네트워크의 파라미터들을 파인튜닝동안 사용하지 않고(freezing) 별도의 소량 가중치(weights)를 부가하여 사용하는 방식이다. 이는 모델이 파인튜닝 기간에 원래 훈련되었던 것을 재앙에 가까울 정도로 잊어버리는 것을 방지하고 저장공간과 계산능력을 대폭 줄였다. 또한 파인튜닝에서 만들어지는 큰 크기의 체크오인트 파일과 달리 수 메가바이트의 작은 체크포인트 파일을 얻을 수 있기때문에 저장 공간 관리에 도움이 된다.

<그림 9. low bit 훈련 준비>

아래 그림10의 ① 부터 ②까지 LoraConfig 를 정의한다. r은 행렬 변경 순위다. 자세한 구성은 여기를 참고. ③에서 LoraConfig 로 모델을 호출 ④에서 모델의 훈련 파라미터를 출력하는 함수를 통해 훈련 파라미터가 ⑤와 같이 6.6GB 에서 6.55MB 크기로 0.099% 축소된 것을 확인할 수 있다.

<그림 10. LoRa 를 위한 준비>

파인튜닝 훈련

아래 그림11과 같이 huggingface 의 Trainer 와 DataCollatorForLanguageModeling을 사용하여 훈련한다. ① 에서 우리는 GPT NeoX 토크나이저(Polyglot 모델이 GPT NeoX 기반)를 사용하고, GPT NeoX 가 EOS 토큰 ID 를 가져야 하므로 PAD에 디폴트 값인 EOS를 부여, ②와 같이 허깅페이스의 transformers 에 최적화된 Trainer class 를 사용한다. ③과 같이 훈련 중의 구성을 TrainingArguments에 명시할 수 있다. 순서대로 훈련 중 기기당 배치 크기, 역전파 시행 전 경사도 수집을 위한 변경 단계 수, 전체 훈련 스텝 수, 학습률, 16비트 정밀도 사용, 100번 마다 로깅, 훈련 결과 디렉토리, 옵티마이저 사용.  ④에서 DataCollator 는 데이터가 같은 길이가 아닐 경우 자동으로 최대 길이로 패딩된다. 마스크언어모델은 사용하지 않기때문에 False 로 세팅. ⑤와 같이하여 warning 메세지를 방지한다. 이후 추론할 때는 다시 True 로 설정 필요. ⑥에서 파인튜닝 훈련이 시작된다.

<그림 11. Trainer 를 통한 훈련>

아래 그림 12와 같이 ① 을 통해 추론 모드로 전환, ②에서 추론을 위해 다시 True로 전환.

<그림 12. 추론모드로 전환>

프롬프트 양식 작성

아래 그림 13과 같이 instruction 과 response 포함한 프롬프트 양식을 작성한다. Alpaca에서 사용되던 프롬프트에 한글을 추가하여 구성하였다. ① 과 ②는 instruction 과 response 사이에 참조할 input 이 있느냐 의 여부로 구별됨. ③은 Alpaca 데이터셋의 영문 instruction 밑에 한글로 번역본을 첨부한 형식. ④는 Instruction, Input, Response 로 출력되는 양식. ⑤에서 작성한 prompt_template 를 custom.json 파일형태로 저장한다.

<그림 13. 프롬프트 양식 작성>

질의에 대한 응답 함수 작성

아래 그림 14와 같이 질문을 입력하면 출력이 되는 함수를 작성한다. ① 과 같이, model.generate 를 통해 생성되는 결과를 응답으로 사용, ② 여기에 입력되는 질문은 tokenizer를 통과하며 ③과 같이, ### 질문: “실제 질문” 라인 바꿔서 “### 답변:” 형식으로 변환되는 return_tensors 는 파이토치 텐서(‘pt’)로 인코딩 된다. ④ 최대 토큰 수는 256 등의 매개변수 선언,  ⑤에서 최대 토큰으로 생성된 첫번째 출력을 토크나이저로 decode 하여 출력한다. 훈련이 충분치 않을 경우에 <|endoftext|> 토큰을 잃어버릴 수 있다. 256 최대 토큰을 사용할 경우, 통상 3분 이상의 생성 시간이 소요된다.

<그림 14. 질의 응답 함수 작성>

응답 함수 출력

아래 그림 15와 같이 응답 함수를 출력한다. ① 출력 소요 시간 측정을 위해 time 설정, ② gen 함수에 질문을 입력하면, ③과 같이 답변이 출력되고 소요시간이 70초(약 1분 10초) 소요된 것으로 나타난다.

<그림 15. 응답 함수 출력>

결언

지금까지 polygot-ko-12.8b 오픈소스 LLM 에 한글 데이터셋으로 메모리 사용량과 훈련 파라미터 크기를 줄이는 4bit quantization 과 QLoRa 기술을 활용하여 한글 질의에 대해 응답하는 오픈소스 경량화 한글 LLM으로 파인튜닝을 하여 모델을 생성하는 과정을 살펴보았다.

2023년 3월 7일 화요일

트랜스포머로 구현하는 영상 언어 질의응답-2편

지난 1편에 이어 2편에서는VQA 데이터셋 생성, 모델 정의, 훈련 및 저장, 그리고 저장된 모델로부터의 응답 추론에 대해 살펴보겠다.

VQA 데이터셋 생성 

1편의 그림 3의 영상-언어 트랜스포머 구조에서 보았듯이 트랜스포머 인코더에 단어 임베딩과 이미지 조각이 펼쳐진 임베딩이 입력된다. Pytorch dataset 을 생성하여 영상, 질문(언어), 응답이 포함된 데이터셋을 생성한다. Pytorch Custom 데이터셋과 데이터로더를 작성하는 방법은 여기를 참조. 이 튜토리얼의 __getitem__ 부분을 아래 그림13과 같이 작성한다. 
 Huggingface 의 ViltProcessor 는 모델의 이미지를 준비하기위한 ViltFeatureExtractor 와 텍스트를 위한 BertToeknizerFast 를 변수로 받아 단일 processor 로 제공한다. ViLT에서 사용될 이미지와 텍스트를 연합한 인코딩 형태의 입력을 만들어준다. 

ViltFeatureExtractor 는 이미지의 크기 변환, 정규화등을 통해 pixel_values 와 pixel_mask 를 생성한다. BertTokenizerFast 는 텍스트를 tokenize 하고 input_ids, attention_mask, token_type_ids 를 생성한다. Tokenizer는 입력 문장을 단어 혹은 서브 단어 단위로 쪼갠 후 사전에 등록된 id로 변환해주는 과정이다. 

Bert Tokenizer 에 대해서는 이전 뉴스레터 16호의 Tokenizer 부분을 참고. 

아래 그림13의 ①은 id_to_filename 을 통해 image 가 위치한 전체 이미지 경로 파일 이름을 획득하여 image 를 호출. ②에서 ViltProcessor에 이미지와 텍스트가 입력된다. 

<그림 13. Pytorch 커스텀 데이터셋으로 작성한 VQAv2 데이터셋 정의 일부>

입력의 최대 길이로 남는 부분은 채워지고(padding), 넘는 부분은 잘라버리고 (truncation), Return tensor 는 Pytorch tensor 로 리턴 한다. ③으로 라벨 크기만큼의 1차원 targets tensor를 만든다. 사용되는 라벨 수는 3129 개다. ④로 3129 라벨에 해당되는 인덱스 위치에 해당 scores 가 기록된다. ⑤라벨 길이만큼의 텐서 targets 이 ‘labels’로 인코딩 된다.  ⑥데이터셋이 인코딩을 리턴 한다.

 아래 그림 14는 VQA데이터셋 생성 정의이다. ①에서 붉은색의 masked language modeling 사전학습으로부터 processor 를 호출한다.  ②앞의 VQA데이터셋 생성의 입력 변수로 questions 의 길이 100까지, annotations 의 길이 100까지로 하여 processor 와 함께 호출한다.

<그림 14. VQA 커스템 데이터셋 생성>

아래 그림 15의 ①에서 첫번째 dataset 의 key 값들이 보여진다. ②에서 processor로 input_ids 인코딩 값을 디코딩한 출력이다. 질문 앞에 [CLS], 문장 끝에 분리 [SEP] 그리고 나머지에 패딩[PAD]가 채워져 있다. ③에 labels 에 있는 인덱스 id 형태 라벨들을 config 에 있는 id2label 을 통해 라벨 값으로 치환한 값들을 출력하고 있다.

<그림 15. dataset 의 input_ids 와 labels>

영상 및 언어 트랜스포머 질의응답 모델 정의

아래 그림 16은 모델 정의 부분이다. ①에서 huggingface 의 transformers로부터 질의응답 모델을 위해 ViltForQuestionAnswering 를 import 한다. ②에서 dandelion/vilt-b32-mlm 즉, vilt 32 patch 크기의 마스크 언어모델 weight 를 사용한 ViltForQuestionAnswering 모델을 정의한다. 이때,  ③라벨 수는 config.id2label 의 길이로 그리고 id2label 과 label2id 를 config 정의를 이용하여 초기화해준다. ④에서 GPU를 사용할 경우 GPU device로 이동하도록 설정한다. 이로써, 모델은 임베딩 층을 거쳐 12개의 multi-head self-attention 층 그리고 마지막으로 linear classifier 가 포함된 모델로 정의된다.

<그림 16. 질의응답 모델 정의>


Pytorch DataLoader 정의

다음으로 데이터셋을 배치로 데이터로드로 호출하는 과정에서 아래 그림 17의 ①과 같이 배치데이터로부터 가장 큰 이미지에 맞추어 패딩과 마스크를 생성 변환하여 배치를 리턴하는 함수 collate_fn 을 정의한다. 이는 모델에 입력되는 이미지의 최대 크기에 맞춰 나머지 차이나는 부분들을 패딩으로 채우고 패딩부분은 마스크가 0으로 표시된다. ②와 같이 배치의 인코딩 item을 ③에서 

<그림 17. 배치 데이터 이미지 픽셀, 마스크 생성 변환 함수>

processor 의 ViltFeatureExtractor class에 있는 pad_and_create_pixel_mask 함수를 이용하여 batch의 픽셀값을 패딩하고 상응하는 픽셀 마스크를 생성한다. 이때 형태는 (batch_size, height, width) 로 어떤 픽셀이 실제(1)이고 어떤 픽셀이 패딩인지(0) 표시된다.  ④에서 변환된 픽셀값과 픽셀마스크 값은 batch dictionary에 순서대로 torch.stack을 통해 쌓이게 된다. ⑤ 에서 dataloader 매개변수로 collate_fn 을 호출하여 pixel_value, pixel_mask 를 변환한 값이 batch에 반영되게 한다.

 아래 그림 18의 ①을 통해 첫번째 interation 으로 데이터로더로부터 데이터를 가져오고, ②에서 픽셀 값이 [3, 384, 544] 로 (앞에 3은 채널) ③에서 픽셀마스크가 [384, 544] 형태로 확인된다. 앞의 4는 batch 크기다. ④에서 라벨 값 3129가 확인된다.

<그림 18. 배치 내의 item 확인>

모델 훈련

아래 그림19 의 ①에서 optimizer로 AdamW, learning rate 는 5e-5 로 설정. epoch를 50으로하고 ②에서 Pytorch training 을 시행한다.

<그림 19.  모델 훈련>

③은 50 epoch 훈련 후의 loss 를 나타낸다. 지금까지 ViLT 사전훈련 모델을 이용하여 질의응답에 파인튜닝하는 절차에 대해 살펴보았다.

추론을 위한 image, question 준비

 자 이제부터는 추론을 위하여 먼저 추론할 image, question을 준비하는 것으로부터 시작해보자. 아래 그림 20의 ①과 같이 COCO 이미지 데이터세트에서 이미지를 하나 선택하고 ②에서 url 로부터 image 를 담게되고, ③에서 연관되는 질문을 생성한다.

<그림 20. Image, question 준비>

image 는 아래 그림 21과 같이 출력된다. 

<그림 21. Image 확인>

ViLTProcessor 를 이용하여 image 와 text 쌍을 준비

  우선 준비된 image 와 text 를 ViLTProcessor을 이용하여 아래 그림 22와 같이 huggingface 의 VQAV2 에 파인튜닝된 ViLT 모델의 image 와 text 쌍을 준비한다. ①과 같이 hugginceface transformers 로 부터 ViLTProcessor 를 import 하고 ②에서 VQAV2에 파인튜닝된 모델로부터 ViLTProcessor 를 로드하여 processor 로 지정한다. 앞에서 살펴본 바와 같이, BertTokenizerFast 토크나이저가 사용되며 input_ids, attention_mask, token_type_ids 를 생성한다. ViltFeatureExtractor 가 이미지 크기 재조정 및 정규화를 담당하며 pixel_values 와 pixel_mask 를 생성한다. 이때 pixel_mask 는 batch 에서만 관련이 있는데, 어떤 픽셀이 실제 픽셀인지 패딩인지 여부에따라 1과 0으로 표시된다. 추론에서는 하나의 예(example)만 다루기때문에 모든 pixel_mask 는 이 경우 1 이다.

<그림 22. Processor 생성>

모델에 입력될 인코딩 준비

  추론을 위하여 준비된 image 와 text쌍을 모델에 입력하기위한 인코딩 작업을 담당할 processor 를 사용하여 아래 그림 23과 같이 encoding 하고 차원을 확인한다. ①과 같이 processor 에 image, text 를 넣어주고 파이썬 텐서를 생성한다. ②에서 인코딩 아이템들의 형태를 표시해본다. ③에서 pixel_values 가 정확히 표시됨을 확인한다. 맨 앞의  batch 값은 추론을 위한 한개의 예만 입력되므로 1 이다.


<그림 23. 입력 인코딩 생성>

ViLT VQAV2 데이터셋에 파인튜닝된 모델 로드

  추론을 위하여 huggingface hub 으로 부터 vilt 모델이 vqav2 에 파인튜닝된 모델을 아래 그림 24와 같이 로드한다. ①과 같이 vqav2 에 파인튜닝된 사전훈련 모델을 사용하여 huggingface 의 ViLTForQuestionAnswering 모델을 로드한다.

<그림 24. 파인튜닝된 모델 로드>

forward pass 를 통한 답변 예측

 그 다음 추론을 위해 아래 그림 25와 같이 모델을 통해 forward pass 를 진행한다. ①과 같이 input_ids, pixel_values 등이 포함된 인코딩을 model 에 forward pass한다. 이때 outputs.logits 의 차원은 (batch_size, num_labels) 의 형태를 띄며 이 경우, (1, 3129)가 된다. VQAV2 의 답변(라벨)은 3129의 가능한 답변이 있다. torch.sigmoid 를 통해 3129개의 라벨에 대해 0 에서 1 사이의 값을 보유하게 되고 최대값이 있는 id 가 idx 로 출력된다. ③에서 idx 에 해당하는 id2label 을 통해 label 값이 출력된다. ④에서 “How many cats are there?” 에 대한 답변이 2 로 출력된다.

<그림. 25. Forward pass>

1편에 이어 2편에서 VQA 데이터셋 생성, 질의응답 모델 정의, 모델 훈련, 추론을 위한 image, text 쌍 준비, 인코딩 준비, 파인튜닝된 모델 로드 그리고 답변 예측에 대해서 살펴보았다.