2024년 6월 24일 월요일

2024년 오픈소스 대형언어모델 소개

이 글은 2024년 오픈소스 대형언어모델을 간략히 소개한다. LLM은 자연어 처리를 위해 개발되었지만, 현재는 멀티모달리티 모델로 발전하고 있다.
다양한 LLM 모델과 자연어 처리
BERT 
BERT 는 Google이 2018년에 도입한 LLM 제품군이다. BERT는 데이터 시퀀스를 다른 데이터 시퀀스로 변환할 수 있는 변환기 기반 모델이다. BERT의 아키텍처는 변압기 인코더의 스택이며 3억 4200만 개의 매개변수를 갖추고 있다. BERT는 대규모 데이터 코퍼스에 대해 사전 훈련된 후 자연어 추론 및 문장 텍스트 유사성과 함께 특정 작업을 수행하도록 미세 조정되었다. 이는 2019년 Google 검색 반복에서 쿼리 이해를 향상시키는 데 사용되었다.

Claude  
Claude  LLM은  AI 보조자가 유용하고 무해하며 정확하도록 돕는 일련의 원칙에 따라 AI 출력을 형성하는 헌법적 AI에 중점을 둔다. Claude는 Anthropic이라는 회사에서 만들어졌다. Claude LLM의 최신 버전은 Claude 3.5 Sonnet이다. LLM의 이전 버전보다 뉘앙스, 유머 및 복잡한 지침을 더 잘 이해하고 Claude 3 Opus의 두 배 속도로 작동하다. Claude.ai 및 Claude iOS 앱을 통해 무료로 사용할 수 있다.

Cohere
Cohere는 Command, Rerank 및 Embed를 포함한 여러 LLM을 제공하는 엔터프라이즈 AI 플랫폼이다. 이러한 LLM은 특정 회사의 사용 사례에 맞게 맞춤 학습되고 미세 조정될 수 있다 . Cohere LLM을 만든 회사는 Attention Is All You Need의 저자 중 한 명이 설립했다. Cohere의 강점 중 하나는 Microsoft Azure 에 바인딩된 OpenAI와 달리 하나의 단일 클라우드에 바인딩되지 않는다는 것이다.

Ernie
Ernie는 Ernie 4.0 챗봇을 지원하는 Baidu의 대규모 언어 모델이다. 이 봇은 2023년 8월에 출시되었으며 4,500만 명 이상의 사용자를 확보했다. 어니는 10조 개의 매개변수를 가지고 있다는 소문이 돌았다. 봇은 중국어에서 가장 잘 작동하지만 다른 언어로도 가능하다.

Falcon 40B
Falcon 40B는 Technology Innovation Institute에서 개발한 변환기 기반의 인과 디코더 전용 모델이다. 오픈 소스이며 영어 데이터로 교육되었다. 이 모델은 Falcon 1B와 Falcon 7B(10억 및 70억 매개변수)의 두 가지 작은 변형으로도 제공된다. Amazon은 Amazon SageMaker 에서 Falcon 40B를 사용할 수 있게 만들었다 . GitHub에서도 무료로 사용할 수 있다.

Gemini
Gemini는 동일한 이름의 회사 챗봇을 지원하는 Google의 LLM 제품군이다. 이 모델은 모델 전환 시 Bard에서 Gemini로 브랜드가 변경된 챗봇을 지원하는 데 Palm을 대체했다. Gemini 모델은 다중 모드이므로 이미지, 오디오, 비디오는 물론 텍스트도 처리할 수 있다. Gemini는 많은 Google 애플리케이션 및 제품에도 통합되어 있다. 울트라, 프로, 나노 세 가지 크기로 제공된다. Ultra는 가장 크고 성능이 뛰어난 모델이고 Pro는 중간 계층 모델이며 Nano는 가장 작은 모델로 기기 내 작업 효율성을 위해 설계되었다. Gemini는 대부분의 평가된 벤치마크에서 GPT-4를 능가하다.

GPT-3
GPT-3는 2020년에 출시된 1,750억 개 이상의 매개변수를 갖춘 OpenAI의 대규모 언어 모델이다. GPT-3은 디코더 전용 변환기 아키텍처를 사용하다. 2022년 9월 Microsoft는 GPT-3의 기본 모델을 독점적으로 사용한다고 발표했다. GPT-3은 이전 제품보다 10배 더 커졌다. GPT-3의 학습 데이터에는 Common Crawl, WebText2, Books1, Books2 및 Wikipedia가 포함된다.

GPT-3은 OpenAI가 매개변수 수를 공개적으로 제공한 GPT 시리즈 모델 중 마지막 모델이다. GPT 시리즈는 2018년 OpenAI의 논문 "Improving Language Understanding by Generative Pre-Training"으로 처음 소개되었다.

GPT-4
GPT-4는 2023년에 출시된 OpenAI의 GPT 시리즈 중 가장 큰 모델이다. 다른 모델과 마찬가지로 트랜스포머 기반 모델 이다 . 다른 모델과 달리 매개변수 개수는 공개되지 않았지만 해당 모델에는 170조 개가 넘는다는 소문이 있다. OpenAI는 GPT-4를 다중 모드 모델로 설명하다. 즉, 언어로만 제한되는 것이 아니라 언어와 이미지를 모두 처리하고 생성 할 수 있다는 의미이다. GPT-4에는 사용자가 음성 톤과 작업을 지정할 수 있는 시스템 메시지도 도입되었다.

GPT-4는 여러 학업 시험에서 인간 수준의 성능을 보여주었다. 모델 출시 당시 일각에서는 GPT-4가 인공지능 (AGI)에 가까워졌다는 추측이 나왔다. 이는 인간과 동등하거나 더 똑똑하다는 의미다. GPT-4는 Microsoft Bing 검색을 지원하며 ChatGPT Plus에서 사용할 수 있으며 최종적으로 Microsoft Office 제품에 통합될 예정이다.

GPT-4o
GPT-4 Omni( GPT-4o )는 OpenAI의 GPT-4 후속 제품이며 이전 모델에 비해 몇 가지 향상된 기능을 제공하다. GPT-4o는 ChatGPT를 위한 보다 자연스러운 인간 상호 작용을 생성하며 오디오, 이미지 및 텍스트를 포함한 다양한 입력을 허용하는 대규모 다중 모드 모델이다. 대화를 통해 사용자는 일반적인 인간 대화처럼 참여할 수 있으며 실시간 상호 작용을 통해 감정을 포착할 수도 있다. GPT-4o는 사진이나 화면을 보고 상호 작용 중에 질문을 할 수 있다.

GPT-4o는 인간의 응답 시간과 비슷하고 GPT-4 Turbo보다 ​​빠른 232밀리초 내에 응답할 수 있다. GPT-4o 모델은 무료이며 개발자 및 고객 제품에 사용할 수 있다.

Lamda
대형 언어 모델 Meta AI(Llama)는 2023년에 출시된 Meta의 LLM이다. 가장 큰 버전은 매개변수 크기가 650억 개이다. Llama는 원래 승인된 연구원 및 개발자에게 출시되었지만 현재는 오픈 소스이다. Llama는 사용, 테스트 및 실험에 더 적은 컴퓨팅 성능이 필요한 더 작은 크기로 제공된다.

Llama는 변환기 아키텍처를 사용하며 CommonCrawl, GitHub, Wikipedia 및 Project Gutenberg의 웹페이지를 포함한 다양한 공개 데이터 소스에 대해 교육을 받았다. Llama는 효과적으로 유출되어 Vicuna와 Orca를 포함한 많은 후손을 낳았다.

Mistral
Mistral은 평가된 모든 벤치마크에서 비슷한 크기의 Llama의 언어 모델보다 성능이 뛰어난 70억 개의 매개변수 언어 모델이다. Mistral은 또한 지침을 따르도록 특화된 미세 조정 모델을 보유하고 있다. 크기가 작기 때문에 비즈니스 목적에 맞는 자체 호스팅 및 유능한 성능이 가능하다. Apache 2.0 라이센스로 출시되었다.

Orca
Orca는 Microsoft에서 개발했으며 130억 개의 매개변수를 가지고 있다. 즉, 노트북에서 실행할 수 있을 만큼 작다. LLM이 달성한 추론 절차를 모방하여 다른 오픈 소스 모델의 발전을 개선하는 것을 목표로 하다. Orca는 훨씬 적은 수의 매개변수로 GPT-4와 동일한 성능을 달성하며 많은 작업에서 GPT-3.5와 동등하다. Orca는 LLaMA의 130억 매개변수 버전을 기반으로 구축되었다.

Palm
Pathways 언어 모델은 AI 챗봇 Bard를 지원하는 Google의 5,400억 매개변수 변환기 기반 모델이다 . Google의 머신러닝용 맞춤 하드웨어인 여러 TPU 4 Pod 에서 학습되었다 . Palm은 코딩, 수학, 분류, 질문 답변 등의 추론 작업을 전문으로 하다. Palm은 복잡한 작업을 간단한 하위 작업으로 분해하는 데에도 탁월하다.

PaLM은 Pathways를 구축하여 궁극적으로 여러 사용 사례의 기반이 되는 단일 모델을 만드는 Google 연구 이니셔티브에서 이름을 따왔다. 생명 과학 및 의료 정보를 위한 Med-Palm 2와 위협 분석 속도를 높이기 위한 사이버 보안 배포를 위한 Sec-Palm을 포함하여 여러 가지 정밀 조정된 버전 의 Palm이 있다 .

Phi
Phi-1은 Microsoft의 변환기 기반 언어 모델이다. 단 13억 개의 매개변수로 Phi-1은 교과서 수준의 데이터 수집에 대해 4일 동안 훈련되었다. Phi-1은 더 나은 품질의 데이터와 합성 데이터에 대해 훈련된 더 작은 모델을 향한 추세의 예이다.
Phi-1은 Python 코딩을 전문으로 하며 크기가 작기 때문에 일반 기능이 적다.

StableLM
StableLM은 이미지 생성기 Stable Diffusion을 개발한 Stability AI에서 개발한 오픈 소스 언어 모델 시리즈이다. 이 글을 쓰는 시점에는 30억, 70억 개의 매개변수 모델이 사용 가능하며 150억, 300억, 650억, 1,750억 개의 매개변수 모델이 진행 중이다. StableLM은 투명하고 접근 가능하며 지원을 제공하는 것을 목표로 하다.

Vicuna
Vicuna는 Llama에서 파생된 또 다른 영향력 있는 오픈 소스 LLM이다. LMSYS에서 개발했으며 sharegpt.com의 데이터를 사용하여 미세 조정되었다. 여러 벤치마크에 따르면 GPT-4보다 작고 성능이 떨어지지만 해당 크기의 모델에는 적합하다. Vicuna에는 330억 개의 매개변수가 있는 반면 GPT-4에는 수조 개의 매개변수가 있다.

이외에, Gemma, Phi2와 같이 3B이하 매개변수를 가지는 소형 LLM도 릴리즈되었다. 이들은 노트북과 같은 온디바이스에 설치되어 사용될 수 있다. 다음은 소형 LLM 간 성능 비교를 보여준다.
소형 LLM 모델 간 성능 비교

2024년 6월 22일 토요일

GPU 거지 딥러닝 서버 만들기

이 글은 GPU 거지?를 위한 딥러닝 서버 구축 방법을 간략히 알아본다. 
많은 정부 과제, 용역, LLM 열풍 등으로 인해 NVIDIA GPU 서버 수요가 폭증하고 있다. 이에 덩달아, GPU 인플레이션이 심한데, 업체에서 구매하면 A100 x 4 way server 가격만 약 1억이다. 이를 개별 부품을 사서 조립하면, 6~7천만원 수준에서 GPU 서버를 얻을 수 있다. 물론, 업체에서 서비스로 설치해주는 리눅스 OS, NVIDIA 드라이버 등 소프트웨어는 본인이 모두 해결해야 단점은 있다. 

필요성
본인이 취미?로 CNN계열 YOLO 모델을 학습, 개발할 때는 그 당시로는 대용량 8GB GPU를 사용하면 나름 빵빵한 스펙이었다. 

그러나, LLM 기술이 릴리즈되면서, 모든 것이 달라졌다. 예를 들어, 70B 파라메터를 가진 LLM 모델을 학습하거나 튜닝하기 위해서는 700GB 이상의 GPU 메모리가 필요하며, 80GB 용량을 가진 NVIDIA A100이 1,300 ~ 1,500만원이니, 대략 2억 가까이되는 리소스가 필요한 상황이 되어버렸다. 
LLM 모델 튜닝, 학습에서 빈번한 Out of Memory 에러

실제로, 배치 데이터를 GPU에 올리고 학습하기 위해서는 이 용량의 몇 배가 필요할 수 있어, 금액은 더 올라가게 되어 있다(국내에서도 이 정도 인프라를 갖춘 IT업체가 많지 않다. 대부분 Lora와 같은 작은 메모리를 이용한 파인튜닝 정도이거나, 해외 빅테크 업체가 개발된 LLM의 사전 학습된 모델을 사용해 튜닝하는 수준이다).

이런 이유로, 본인이 LLM모델을 직접 개발하고 싶다면, 가성비있게 다중 GPU지원하는 서버를 개발할 필요가 생긴다. 

다중 GPU 서버 구축하기
다중 GPU 구축할 때 주요 고려 사항은 다음과 같다.
  • 메모리(VRAM)
  • 성능(Tensor 코어, 클럭 속도)
  • 슬롯 너비
  • 사용 전력
  • 다중 GPU 지원 마더보드
  • 냉각 처리
  • 넉넉한 케이스 크기
딥러닝 작업을 위해서는 많은 메모리가 필요하다. LLM은 미세 조정하기에도 방대하며, 컴퓨터 비전 작업은 특히 3D 네트워크에서 메모리 집약적일 수 있다. 당연히 찾아야 할 가장 중요한 측면은 GPU VRAM이다. LLM의 경우 최소 24GB 메모리를 권장하고 컴퓨터 비전 작업의 경우 12GB 이하로 내려가지 않는다.

두 번째 기준은 FLOPS(초당 부동 소수점 연산)로 추정할 수 있는 성능이다.
과거의 중요한 숫자는 회로의 CUDA 코어 수였다. 그러나 딥 러닝의 등장으로 NVIDIA는 클럭당 더 많은 FMA(Fused Multiply-Add) 연산을 수행할 수 있는 특수 텐서 코어를 도입했다. 

다른 GPU의 성능을 비교할 때는 각별히 주의해야 하다. 다른 세대/아키텍처의 Tensor 코어는 비교할 수 없다. 예를 들어, A100은 256개의 FP16 FMA 작동/클럭을 수행하는 반면 V100은 64개의 "유일한" 작업을 수행한다. 또한 이전 아키텍처(Turing, Volta)는 32비트 텐서 연산을 지원하지 않는다. 비교를 더 어렵게 만드는 것은 NVIDIA가 백서에서도 항상 FMA를 보고하는 것은 아니며 동일한 아키텍처의 GPU가 다른 FMA를 가질 수 있다는 것이다. 참고로, GPT는 수많은 A100을 사용해 학습되었다.
NVIDIA A100 (80GB. 가격은 12,000 ~ 15,000만원 사이)

멀티 GPU 시스템을 구축할 때는 GPU를 PC 케이스에 물리적으로 맞추는 방법을 계획해야 하다. GPU가 점점 더 커지고 있기 때문에, 특히 게임 시리즈에서는 이것이 더 큰 문제가 되고 있다. 소비자용 마더보드에는 최대 7개의 PCIe 슬롯이 있으며 PC 케이스는 이 설정을 기반으로 제작됩니다. 4090은 제조업체에 따라 4개의 슬롯을 쉽게 차지할 수 있으므로 이것이 문제가 되는 이유를 알 수 있다. 또한 과열을 방지하기 위해 송풍기 스타일이 아니거나 수냉식이 아닌 GPU 사이에 최소 1개의 슬롯을 남겨 두어야 하다. 다음과 같은 옵션이 있다.

수냉식은 최대 2개의 슬롯을 차지하고 비싸다. AIO(All-in-One) 솔루션을 얻지 못하면 맞춤형 수냉식 루프를 구축해야 하다. AIO 라디에이터가 케이스에 맞지 않을 수 있으므로 여러 개의 수냉식 GPU를 장착하려는 경우에도 마찬가지이다. 

최신 GPU는 점점 더 많은 전력을 소비하다. 예를 들어, 4090은 450W가 필요하고 H100은 최대 700W를 얻을 수 있다. GPU가 끌어올 수 있는 최대 전력을 줄이는 데 필요한 것은 다음과 같다.

sudo nvidia-smi -i <GPU_index> -pl <power_limit>

다음 단계는 여러 GPU를 허용하는 마더보드를 선택하는 것이다. 여기서 주요 고려 사항은 PCIe 레인이다. 각 카드에 대해 각각 x8 레인이 있는 최소 PCIe 3.0 슬롯이 필요하다. PCIe 4.0 또는 5.0은 더 드물며 대부분의 딥 러닝 사용 사례에 필요하지 않는다. 간격을 확인하고 GPU가 실제로 원하는 곳으로 이동할 수 있는지 확인하라. 

마더보드는 여러 GPU를 연동하기 위한 Multiple GPU와, SLI 혹은 NVLink 규약을 지원해야 한다. 이 규약은 다중 GPU 간 메모리를 합치고, 공유하며, 이들간 데이터 전송 속도를 최적화한다. 다음은 그 리스트를 보여준다. 

마무리
일반적으로 판매용 GPU서버, 서버랙 순서로 가격이 비싸다. 앞의 고려사항이 복잡하다면, GPU 서버 랙 구입을 감안할 수 있다. 좀 더 비싸지만, 안정적이다. 다음은 이런 부분이 고려된 GPU 서버를 보여준다.

레퍼런스

2024년 6월 21일 금요일

Weights & Biases로 딥러닝 모델 개발 프로세스 기록, 분석, 가시화 및 모델 튜닝하기

딥러닝 모델들을 개발하다 보면, 수많은 종류의 데이터셋, 하이퍼모델 파라메터 튜닝 등으로 인해 관리해야 할 자료들이 매우 복잡해진다는 것을 알게 된다. Weights & Biases (W&B) 회사는 이름 그대로 완벽한 모델 학습을 위해 필요한 Weights & Biases를 모니터링, 관리할 수 있는 로그 도구이다. 즉, 딥러닝 모델 개발자를 위한 프로세스 로그 및 가시화 플랫폼을 제공한다. 
W&B(AI Summer)

매우 직관적인 이름을 가진 이 스타트업은 Tensorboard와 유사하지만, 적은 코드로 모델 개발에 많은 통찰력을 준다. 이 W&B(WandB) 라이브러리를 사용하면, 딥러닝 모델 학습 시 지저분하게 붙어 나가는 로그 처리를 매우 간단한 함수 몇 개로 처리할 수 있다. 통합된 데시보드 형태로 다양한 모델 학습 품질 지표를 확인 및 비교할 수 있다. 이외, 학습 모델 하이퍼 파라메터 관리와 튜닝 및 비교 보고서 생성 기능을 제공한다. 로그는 숫자, 텍스트, 이미지 등 다양한 포맷을 지원한다. 
W&B 딥러닝 모델 개발 프로세스 가시화 데쉬보드

이 글은 딥러닝 모델 학습 로그, 가시화만 집중해 살펴본다. 글 마무리에 W&B의 개발 배경도 간단히 알아본다.  

사용법
다음 링크 방문해 회원 가입한다. 
회원 가입하고, 다음 그림과 같이 홈 메뉴에서 키 토큰 값을 얻어 복사한다. 이 키는 wandb API를 사용할 때 필요하다.

터미널에서 다음을 실행해 설치한다. 
pip install wandb

예시 및 결과
다음과 같이 cosine 데이터를 학습하는 간단한 코드를 만들어 본다. W&B로 로그를 기록하도록 몇몇 함수를 호출할 것이다. 앞에서 얻은 키 토큰은 다음 코드에 해당 부분에 입력해 준다.

import os
from tqdm import tqdm
import matplotlib.pyplot as plt
import numpy as np
import wandb
import torch
import torch.nn as nn
import torch.optim as optim
from torchviz import make_dot

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"Using device: {device}")

class SimpleMLP(nn.Module):
def __init__(self):
super(SimpleMLP, self).__init__()
self.fc1 = nn.Linear(1, 50)
self.fc2 = nn.Linear(50, 1)

def forward(self, x):
x = torch.relu(self.fc1(x))
x = self.fc2(x)
return x

# Generate cosine dataset
def generate_cosine_data(num_samples=100):
x = torch.linspace(-2 * torch.pi, 2 * torch.pi, num_samples).view(-1, 1)
y = torch.cos(x)
return x, y

# Instantiate the model, loss function, and optimizer
model = SimpleMLP()
model.to(device)
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)

wandb.login(key='') # 여기에 키값 입력
api = wandb.Api()
wandb.init(project="train_cosin", 
config={
"optimizer": "SGD",
"learning_rate": 0.01,
"architecture": "SimpleMLP",
"dataset": "cosine"
})
wandb.watch(model, criterion, log="all")  # 모든 지표 기록

# 학습 데이터 생성
x, y = generate_cosine_data()
x, y = x.to(device), y.to(device)

# 학습
prediction = None
num_epochs = 10000
image_files = []
for epoch in tqdm(range(num_epochs)):
# Forward pass
outputs = model(x)
prediction = outputs
loss = criterion(outputs, y)
print(f"Epoch: {epoch}, Loss: {loss.item()}")

# Backward pass and optimization
optimizer.zero_grad()
loss.backward()
optimizer.step()
# Visualize the gradients of the first epoch using wandb
layer_name = ''
grad_cpu = data_cpu = None
for name, param in model.named_parameters():
if param.requires_grad and param.grad is not None:
layer_name = name
grad_cpu = param.grad.detach().cpu()
data_cpu = param.data.detach().cpu()
break

log_data = {"epoch": epoch, "loss": loss.item(), "outputs": outputs.detach().cpu().numpy(), 
f'{layer_name}_gradients': wandb.Histogram(grad_cpu), 
f'{layer_name}_weights': wandb.Histogram(data_cpu)}
wandb.log(log_data) # 에폭, 로스, 출력, 기울기, 가중치 로그 기록

학습된 결과는 다음과 같다. 잘 학습된 것을 알 수 있다.

W&B 웹사이트 나의 프로젝트의 데쉬보드를 확인한다. 

데쉬보드에서는 각 단계 별 기울기, 가중치, 로스 등이 어떻게 변화하는 지를 손쉽게 확인할 수 있다. 

각 차트 데이터는 엑셀 등 포맷으로 저장 가능하다. 

로짓 출력값을 확인해 보자. 초기 에폭에서는 학습되지 않은 임의 값을 출력하지만, 학습될 수록 y에 근사한 패턴으로 출력되는 것을 확인할 수 있다.
엑셀 출력된 데이터를 보면, cosine 패턴으로 수렴한 예측값이 점차 많아지는 것을 확인할 수 있다. 

다음 경우는 모델의 바이어스 히스토그램 차트 데이터를 보여준다. 

엑셀로 다운로드하면, 모델 학습 데이터의 특정 범위에 속한 값의 누적 데이터를 쉽게 확인할 수 있다.

다음과 같이 리포트 기능을 사용해, 모델 학습 품질을 검토할 수 있다.

마무리
W&B는 루카스 비왈드(Lukas Biewald)가 2017년 설립한 딥러닝 모델 학습 서비스를 제공하는 스타트업이다. 이 회사는 딥러닝 개발자에 필요한 실무적인 도구를 개발한다. 그는 딥러닝 연구 초창기부터 머신러닝 연구자 및 개발자로 있으면서, 실무자의 어려움을 알고 있었다. 
Lukas Biewald (2018)

OpenAI는 W&B의 첫번째 고객이다. 이 도구는 머신러닝 개발자의 많은 수작업 프로세스를 자동화하여, 모델 학습 과정을 모니터링, 추적하고, 직관적인 학습 모델 디버깅, 검사 및 설명이 가능하도록 하여, 체계적으로 이 과정을 관리한다. 이 회사는 현재 700,000명 이상의 유료 사용자를 보유하고 있으며, 대부분, OpenAI와 같이 빅테크 기업들 개발자이 사용자이다. 
이 회사는 현재까지 $250M 펀딩 투자받았다. 이 금액은 원화로 3,500억(환율 1400원 기준)에 해당한다. 현재 W&B는 핵심기술을 개발하며, 기업공개를 준비하고 있다.
부록: 딥러닝 모델의 해 탐색 과정 탐색 과정 가시화 원리
여기서 딥러닝 모델의 해 탐색 과정을 가시화하는 원리를 간략히 살펴보자. 딥러닝 모델은 빅 데이터를 통계적 학습하여, y에 가장 가까운 ŷ = w·x + b 를 탐색하는 과정이다. 그러므로, 가장 loss = y - ŷ가 작은 weight w, bais b를 찾는 것이 목적이다. 그러므로, 제대로 해를 탐색하는 지 확인하려면, epoch당 loss와 w:b 차트를 확인하는 것이 중요하다. 

모델을 구성하는 레이어 유닛이 여러개라면, w:b도 여기에 비례해 많으므로, 원리만 살펴보기 위해, 매우 간단한 ŷ = w·x + b 수식을 학습하는 단순한 딥러닝 모델을 학습한다고 가정한다.

# 데이터 생성
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(20240215)
n = 50
x = np.array(np.random.randn(n), dtype=np.float32)
y = np.array(
  0.75 * x**2 + 1.0 * x + 2.0 + 0.3 * np.random.randn(n),
  dtype=np.float32) # 데이터 임의로 생성할 수식

plt.scatter(x, y, facecolors='none', edgecolors='b')
plt.scatter(x, y, c='r')
plt.show()

# 데이터 학습 모델 준비
import torch

model = torch.nn.Linear(1, 1)
model.weight.data.fill_(6.0)
model.bias.data.fill_(-3.0)

# 손실 함수 준비
loss_fn = torch.nn.MSELoss()
learning_rate = 0.1
epochs = 100
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

# 학습
models = [[model.weight.item(), model.bias.item()]]
for epoch in range(epochs):
  inputs = torch.from_numpy(x).requires_grad_().reshape(-1, 1)
  labels = torch.from_numpy(y).reshape(-1, 1)

  optimizer.zero_grad()
  outputs = model(inputs)
  loss = loss_fn(outputs, labels)
  loss.backward()
  optimizer.step()
  print('epoch {}, loss {}'.format(epoch, loss.item()))
  models.append([model.weight.item(), model.bias.item()])

# 모델 예측 값 비교 출력
weight = model.weight.item()
bias = model.bias.item()
plt.scatter(x, y, facecolors='none', edgecolors='b')
plt.plot(
  [x.min(), x.max()],
  [weight * x.min() + bias, weight * x.max() + bias],
  c='r')
plt.show()

# loss map 작성
def get_loss_map(loss_fn, x, y):
  """Maps the loss function on a 100-by-100 grid between (-5, -5) and (8, 8)."""
  losses = [[0.0] * 101 for _ in range(101)]
  x = torch.from_numpy(x)
  y = torch.from_numpy(y)
  for wi in range(101):
    for wb in range(101):
      w = -5.0 + 13.0 * wi / 100.0
      b = -5.0 + 13.0 * wb / 100.0
      ywb = x * w + b
      losses[wi][wb] = loss_fn(ywb, y).item()

  return list(reversed(losses))  # Because y will be reversed.

# w:b 2차원 loss map 상 해 탐색 path 가시화 
import pylab

loss_fn = torch.nn.MSELoss()
losses = get_loss_map(loss_fn, x, y)
cm = pylab.get_cmap('terrain')
fig, ax = plt.subplots()
plt.xlabel('Bias')
plt.ylabel('Weight')
i = ax.imshow(losses, cmap=cm, interpolation='nearest', extent=[-5, 8, -5, 8])

model_weights, model_biases = zip(*models)
ax.scatter(model_biases, model_weights, c='r', marker='+')
ax.plot(model_biases, model_weights, c='r')

fig.colorbar(i)
plt.show()

결과는 다음과 같다. 
loss함수를 바꿔가며, 해가 어떻게 탐색되는 지 확인해 보면, 다음과 같다. 데이터 특성에 따라 적절한 전략을 써야 한다는 것을 알 수 있다. 
이 경우에는 w, b 인 2차원이라 시각화가 쉬웠으나, 3차원 이상이면, 다른 가시화 방법을 사용해야 한다. 

레퍼런스

2024년 6월 20일 목요일

도메인 모델 성능개선을 위한 Lora, LLAMA3 기반 LLM 만들기

이 글은 Lora 기반 LLAMA3 모델 파인튜닝하는 방법을 간략히 보여준다. 이를 통해, 특정 도메인의 LLM 모델 생성 정확도를 향상시킬 수 있다.


머리말
LLAMA3는 메타가 개발한 LLM 제품이다. 모델은 15조 개의 토큰으로 구성된 광범위한 데이터 세트에서 훈련되었다(라마 2의 경우 2T 토큰과 비교). 700억 개의 파라미터 모델과 더 작은 80억 개의 파라미터 모델의 두 가지 모델 크기가 출시되었다. 70B 모델은 MMLU 벤치마크에서 82점, HumanEval 벤치마크에서 81.7점을 기록하며 이미 인상적인 성능을 보여주었다.

라마 3 모델은 컨텍스트 길이를 최대 8,192개 토큰(라마 2의 경우 4,096개 토큰)까지 늘렸으며, RoPE를 통해 최대 32k까지 확장할 수 있다. 또한 이 모델은 128K 토큰 어휘가 있는 새로운 토크나이저를 사용하여 텍스트를 인코딩하는 데 필요한 토큰 수를 15% 줄이다. 

파인튜닝 개념
파인튜닝(fine turning. 미세 조정)전략은 활용된 데이터에 따라 달라지며, 이는 네 가지 유형으로 분류할 수 있다.
  • 감독 미세 조정
  • 퓨샷 학습(Few-shot Learning)
  • 전체 전이 학습
  • 도메인별 미세 조정
  • 감독 미세 조정
이 방법은 미세 조정에 대한 표준 접근 방식을 나타낸다. 모델은 텍스트 분류, 질문 답변 또는 명명된 엔터티 인식과 같이 수행하려는 특정 작업에 맞게 조정된 레이블이 지정된 데이터 세트를 사용하여 추가 학습을 거친다. 예를 들어 감정 분석에서 모델은 해당 감정으로 주석이 달린 텍스트 샘플로 구성된 데이터 세트에서 학습된다.

퓨샷 학습(Few-Shot Learning)
레이블이 지정된 대규모 데이터 세트를 어셈블하는 것이 비현실적인 것으로 판명된 시나리오에서는 솔루션을 제공하기 위해 몇 가지 학습 단계가 개입한다. 이 기술은 입력 프롬프트를 시작할 때 원하는 작업에 대한 몇 가지 예제(또는 샷)를 모델에 제공한다. 이렇게 함으로써 모델은 철저한 미세 조정 요법 없이 작업에 대한 더 나은 컨텍스트 이해를 얻을 수 있다.

few_shot_examples = [
{"input":"Could you please clarify the terms outlined in section 3.2 of the contract?",
"output":"Certainly, I will provide clarification on the terms in section 3.2."},
{"input":"We are interested in extending the payment deadline to 30 days instead of the current 15 days. Additionally, we would like to add a clause regarding late payment penalties.",
"output":"Our request is to extend the payment deadline to 30 days and include a clause on late payment penalties."},
{"input":"""The current indemnification clause seems too broad. We would like to narrow it down to cover only direct damages and exclude consequential damages.
Additionally, we propose including a dispute resolution clause specifying arbitration as the preferred method of resolving disputes.""",
"output":"""We suggest revising the indemnification clause to limit it to covering direct damages and excluding consequential damages.
Furthermore, we recommend adding a dispute resolution clause that specifies arbitration as the preferred method of resolving disputes."""},
{"input":"I believe the proposed changes are acceptable.",
"output":"Thank you for your feedback. I will proceed with implementing the proposed changes."}
]

전체 전이 학습
모든 미세 조정 방법에는 일종의 전이 학습이 포함되지만, 이 범주는 특히 모델이 원래 학습 목표와 다른 작업을 수행할 수 있도록 한다. 핵심은 광범위하고 일반적인 데이터 세트에서 모델이 축적한 지식을 활용하여 보다 전문적이거나 관련 작업에 적용하는 데 있다.

도메인별 미세 조정
이 미세 조정 변형은 특정 도메인 또는 산업과 관련된 텍스트를 이해하고 생성하도록 모델을 적응시키는 것을 목표로 한다. 모델은 대상 도메인과 관련된 텍스트로 구성된 데이터 세트를 사용하여 미세 조정을 거치므로 도메인별 작업에 대한 컨텍스트 파악과 숙련도가 향상된다. 예를 들어, 의료 애플리케이션용 챗봇을 개발하기 위해 모델은 의료 기록에 대해 훈련되어 의료 영역 내에서 언어 이해 능력을 개선해야 한다.

미세 조정 과정에서 업데이트되는 모델 가중치에 따라 두 가지 유형의 미세 조정이 있다.

파라미터 효율 미세 조정(PEFT)
전체 미세 조정이라고 하는 이 포괄적인 미세 조정 방법에는 모든 모델 가중치를 업데이트하여 최적화된 버전을 만드는 작업이 포함된다. 그러나 사전 학습과 유사한 메모리 및 계산 리소스에 대한 상당한 요구 사항을 부과하므로 학습 중에 저장 및 처리를 관리하기 위한 강력한 인프라가 필요한다.

파라미터 효율 미세 조정(PEFT)
매개 변수 효율적인 미세 조정 또는 간단히 PEFT는 명령 미세 조정 방법론에서 전체 미세 조정에 대한 보다 리소스 효율적인 대안을 나타낸다. 전체 LLM 미세 조정은 상당한 계산 오버헤드를 수반하여 메모리 할당에 문제를 제기하는 반면, PEFT는 매개변수의 하위 집합만 업데이트하고 나머지는 효과적으로 "고정"하는 솔루션을 제공한다. 이 접근 방식은 학습 가능한 매개 변수의 수를 줄여 메모리 요구 사항을 완화하고 치명적인 망각을 방지한다. 완전한 미세 조정과 달리 PEFT는 이전에 습득한 지식을 유지하면서 원래 LLM 가중치를 보존한다. 이 기능은 여러 작업을 미세 조정할 때 스토리지 제약 조건을 완화하는 데 유리한 것으로 입증되었다. LoRA(Low-Rank Adaptation) 및 QLoRA(Quantized Low-Rank Adaptation)와 같이 널리 채택된 기술은 파라미터 효율적인 미세 조정을 달성하기 위한 효과적인 방법을 보여준다.

ReFT
언어 모델은 토큰 시퀀스의 상황에 맞는 표현을 생성해야 한다. 
ReFT(표현 미세 조정)은 n개 입력 토큰 x = (x1, . . . , xn)의 시퀀스가 주어지면 모델은 먼저 이러한 토큰을 표현 목록에 포함한다. 그런 다음, 모델 계층은 은닉 표현의 j번째 리스트 h(j)를 이전 은닉 표현 리스트 h(j−1)의 함수로 연속적으로 계산한다. 각 은닉 표현은 벡터이다. LM은 최종 은닉 표현을 사용하여 예측을 생성한다.

PyReFT는 학습 개입을 통해 내부 언어모델 표현적응을 지원하는 표현 미세 조정(ReFT) 라이브러리이다. 적은 수의 미세 조정 매개변수와 강력한 성능을 통해 Pyreft는 미세 조정 효율성을 높이고 미세 조정 비용을 줄일 수 있다.

LoRA 및 QLoRa
LoRA는 향상된 미세 조정 접근 방식으로, 사전 훈련된 대규모 언어 모델의 가중치 행렬에 근접한 두 개의 작은 행렬만 미세 조정하여 LoRA 어댑터를 형성함으로써 기존 방법에서 벗어난다. 그런 다음 이 미세 조정된 어댑터는 후속 추론 작업을 위해 미리 학습된 모델에 통합된다. 특정 작업 또는 사용 사례에 대한 LoRA 미세 조정이 완료되면 그 결과는 변경되지 않은 원본 LLM과 함께 훨씬 더 작은 "LoRA 어댑터"의 출현으로 이어지며, 이는 종종 원래 LLM 크기(GB가 아닌 MB로 측정됨)의 일부에 불과한다. 추론하는 동안 LoRA 어댑터는 원래 LLM과 융합되어야 한다. 이 접근 방식은 많은 LoRA 어댑터가 원래 LLM의 용도를 효과적으로 변경할 수 있으므로 여러 작업 및 사용 사례를 처리할 때 전체 메모리 요구 사항을 줄일 수 있다는 주요 이점을 제공한다.

QLoRA는 LoRA에 비해 메모리 효율성이 더욱 향상되었음을 나타낸다. LoRA 어댑터의 가중치를 더 낮은 정밀도(일반적으로 원래 4비트 대신 8비트)로 양자화하여 LoRA 기술을 개선한다. 이 추가 최적화는 메모리 공간과 스토리지 오버헤드를 크게 줄이다. QLoRA에서 사전 훈련된 모델은 양자화된 4비트 가중치로 GPU 메모리에 로드되며, 이는 LoRA에서 사용되는 8비트 정밀도에서 벗어난다. 이러한 비트 정밀도 감소에도 불구하고 QLoRA는 이전 제품과 비슷한 수준의 효율성을 유지하여 성능 저하 없이 메모리 사용을 최적화하는 능력을 보여준다.

라마3 파인튜닝 모델 개발 
개발 환경
앞의 내용을 고려해, 라마3를 파인튜닝한다. 개발환경은 다음과 같다.
pip install -U transformers 
pip install -U datasets 
pip install -U accelerate 
pip install -U peft 
pip install -U trl 
pip install -U bitsandbytes 
pip install -U wandb

저렴한 비용으로 학습 모델을 개발하기 위해, Lora, 양자화를 사용한다. 사전 학습모델은 허깅페이스(HF)에서 제공하는 LLAMA3-8B모델을 사용한다. 

파인튜닝할 데이터는 ruslanmv/ai-medical-chatbot · Datasets at Hugging Face 를 사용한다. 

구현 코드는 다음과 같다.
# 파이썬 패키지 임포트
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    BitsAndBytesConfig,
    HfArgumentParser,
    TrainingArguments,
    pipeline,
    logging,
)
from peft import (
    LoraConfig,
    PeftModel,
    prepare_model_for_kbit_training,
    get_peft_model,
)
import os, torch, wandb
from datasets import load_dataset
from trl import SFTTrainer, setup_chat_format

# 허깅페이스및 W&B 키 토큰 이용해 로그인. 미리 사전 발급받아야 함.
from huggingface_hub import login # https://www.kaggle.com/discussions/product-feedback/114053
login(token = 'input your HF token')  # HF 키 토큰 입력

wandb.login(key='input your WandB token')  # W&B 키 토큰 입력
run = wandb.init(
    project='Fine-tune Llama 3 8B on Medical Dataset', 
    job_type="training", 
    anonymous="allow"
)

# 사전학습모델 설정
from transformers import AutoTokenizer, AutoModelForCausalLM

base_model = "Undi95/Meta-Llama-3-8B-hf" 
dataset_name = "ruslanmv/ai-medical-chatbot" # 사용할 데이터셋
new_model = "llama-3-8b-chat-doctor"           # 파인튜닝 모델 이름

torch_dtype = torch.float16
attn_implementation = "eager"

# QLoRA 설정
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True, # 4bit 양자화
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch_dtype,
    bnb_4bit_use_double_quant=True,
)

model = AutoModelForCausalLM.from_pretrained(
    base_model,
    quantization_config=bnb_config,
    device_map="auto",
    attn_implementation=attn_implementation
)

# 토크나이저 및 모델 로딩
tokenizer = AutoTokenizer.from_pretrained(base_model)
model, tokenizer = setup_chat_format(model, tokenizer)

# Lora 설정
peft_config = LoraConfig(
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
    target_modules=['up_proj', 'down_proj', 'gate_proj', 'k_proj', 'q_proj', 'v_proj', 'o_proj']
)
model = get_peft_model(model, peft_config)  # 파라메터 튜닝 설정

# 데이터셋 로딩
dataset = load_dataset(dataset_name, split="all")
dataset = dataset.shuffle(seed=65).select(range(1000)) # 파인튜닝 예시 위해 1000개만 사용

def format_chat_template(row):
    row_json = [{"role": "user", "content": row["Patient"]},
               {"role": "assistant", "content": row["Doctor"]}]
    row["text"] = tokenizer.apply_chat_template(row_json, tokenize=False)
    return row

dataset = dataset.map(
    format_chat_template,
    num_proc=4,
)

print(dataset['text'][3])

dataset = dataset.train_test_split(test_size=0.1) # 데이터 검증 세트 분할

training_arguments = TrainingArguments(
    output_dir=new_model,
    per_device_train_batch_size=1,
    per_device_eval_batch_size=1,
    gradient_accumulation_steps=2,
    optim="paged_adamw_32bit",
    num_train_epochs=1,
    evaluation_strategy="steps",
    eval_steps=0.2,
    logging_steps=1,
    warmup_steps=10,
    logging_strategy="steps",
    learning_rate=2e-4,
    fp16=False,
    bf16=False,
    group_by_length=True,
    report_to="wandb"
)

# 지도미세조정(SFT) 설정
trainer = SFTTrainer(
    model=model,
    train_dataset=dataset["train"],
    eval_dataset=dataset["test"],
    peft_config=peft_config,
    max_seq_length=512,
    dataset_text_field="text",
    tokenizer=tokenizer,
    args=training_arguments,
    packing= False,
)

# 미세조정 모델학습
trainer.train()

# W&B 로그 종료
wandb.finish()
model.config.use_cache = True

# 테스트
messages = [
    {
        "role": "user",
        "content": "Hello doctor, I have bad acne. How do I get rid of it?"
    }
]
prompt = tokenizer.apply_chat_template(messages, tokenize=False, 
                                       add_generation_prompt=True)
inputs = tokenizer(prompt, return_tensors='pt', padding=True, 
                   truncation=True).to("cuda")
outputs = model.generate(**inputs, max_length=150, 
                         num_return_sequences=1)
text = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(text.split("assistant")[1])

# 파인튜닝된 모델을 허깅페이스 허브에 업로드함
trainer.model.save_pretrained(new_model)
trainer.model.push_to_hub(new_model, use_temp_dir=False)

마무리
실행 결과는 다음과 같다. 
레퍼런스
다음은 파인튜닝 후 RAG처리 전략에 대한 레퍼런스이다. 

2024년 6월 19일 수요일

NLP의 핵심. 토큰, 임베딩 모델 파인튜닝

이 글은 LLM NLP처리의 핵심인 토큰, 임베딩 모델 파인튜닝에 대한 내용을 간략히 다룬다. 여기서 토큰은 문장을 구성하는 단어로 가정하면 이해하기 쉽다. 토큰과 임베딩은 입력 시퀀스에 대한 출력을 학습, 예측할 때 훈련의 전제가 되는 LLM의 기본조건이다. 이에 대해 좀 더 깊게 이해해 보자.

TIKTOKEN 라이브러리

도메인 의존 정보와 토큰
의학과 같은 특별한 분야에서는 환각 현상 등으로 인해 ChatGPT와 같은 범용 LLM이 제대로 정보를 생성하지 못하는 경우가 많다. 이런 문제를 해결하기 위해, 전문 분야의 지식을 기존 LLM 모델을 이용해 재학습하는 방법이 생겨났는 데, 파인튜닝은 그 중에 한 방법이다. 

임베딩은 학습 모델이 입력되는 문장의 토큰 패턴을 통계적으로 계산하기 전, 토큰을 수치화시키는 함수이다. 임베딩 모델은 다양한 종류가 있다. 임베딩 모델은 토큰을 수치화하여 모델 학습에 사용하는 데 필요한 입력값을 출력한다. 이런 이유로, 토큰 사전과 임베딩 모델이 다르면, 제대로 된 모델 학습, 예측, 패턴 계산 결과를 얻기 어렵다. 다음 그림은 토큰이 수치화된 결과를 보여준다. 참고로, 토큰이 숫자로 표현되지 못하는 문제를 OOV(Out-Of-Vocabulary라 한다.
숫자 토큰화 결과

이런 이유로, LLM을 재학습하기 전에 어떤 토큰이 사용되었는 지, 임베딩 모델이 무엇인지 확인해야 한다. 일반적으로, 모델을 파인 튜닝하려면, LLM 토큰 확인 및 개발, 임베딩 모델의 적절한 사용이 필요하다. 

개발환경
실습을 위해 다음을 설치한다.
pip install transformers torch

참고로, 다음은 파인튜닝에 사용하는 도구를 보여준다.
  • Torch: 텐서 계산 및 딥 러닝을 위한 핵심 라이브러리이다.
  • peft: 낮은 순위의 적응 기술을 사용하여 대규모 언어 모델을 효율적으로 미세 조정할 수 있다. 특히 리소스가 제한된 장치에서 학습 가능한 매개 변수의 수를 줄여 모델을 압축하고 더 빠르게 미세 조정할 수 있다.
  • bitsandbytes: 신경망에 대한 양자화 및 이진화 기술을 제공하여 모델 압축을 지원한다. 모델 압축에 도움이 되므로 메모리와 계산 능력이 제한된 에지 장치에 모델을 보다 실현 가능하게 만들 수 있다.
  • Transformers: 대규모 언어 모델 작업을 간소화하여 사전 학습된 모델 및 학습 파이프라인을 제공한다.
  • trl: 대규모 언어 모델의 경우 효율적인 모델 학습 및 최적화에 중점을 둔다.
  • accelerate: 다양한 하드웨어 플랫폼에서 학습 및 추론을 가속화한다.
  • dataset: 기계 학습 작업을 위한 데이터 세트 로드 및 준비를 간소화한다.
  • pipeline: 사용자 지정 학습 없이 일반적인 NLP 작업에 대해 사전 학습된 모델의 사용을 간소화한다.
  • pyarrow: 효율적인 데이터 로드 및 처리를 위해 사용될 수 있다.
  • LoraConfig: LoRA 기반 미세 조정을 위한 구성 매개변수를 보유한다.
  • SFTTrainer: 모델 학습, 최적화 및 평가를 처리한다.
토큰 사전 
LLM 파인튜닝이나 RAG 시 토큰 사전이 없으면, 제대로 학습되지 않는다. 입력 시퀀스의 토큰이 사전에 없으면, 토큰은 분리된다. 분리된 토큰들은 각자 다른 맥락을 가지도록 학습된다. 다음 코드를 실행하면, 그 내용을 확인할 수 있다. 이런 문제는 유전자 해석 등 다양한 문제에서 발생된다. 
from transformers import DistilBertTokenizerFast, DistilBertModel

tokenizer = DistilBertTokenizerFast.from_pretrained("distilbert-base-uncased")
tokens = tokenizer.encode('This is a IfcBuilding.', return_tensors='pt')
print("These are tokens!", tokens)
for token in tokens[0]:
    print("This are decoded tokens!", tokenizer.decode([token]))

model = DistilBertModel.from_pretrained("distilbert-base-uncased")
print(model.embeddings.word_embeddings(tokens))
for e in model.embeddings.word_embeddings(tokens)[0]:
    print("This is an embedding!", e)

토큰은 Subword 또는 Byte Pair Encoding되어 다음과 같이 출력될 것이다. 

다음 코드를 실행해보면, 좀 더 많은 문제점을 확인할 수 있다.
from transformers import BertTokenizer, BertModel
bert_tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')

example_sen = (
    """
    The United States and Russia sought to lower the temperature in a 
    heated standoff over Ukraine,even as they reported no breakthroughs 
    in high-stakes talks on Friday aimed at preventing a feared Russian invasion
    """
)
print(bert_tokenizer.tokenize(example_sen))

결과는 다음과 같다. 
['the', 'united', 'states', 'and', 'russia', 'sought', 'to', 'lower', 'the', 'temperature', 'in', 'a', 'heated', 'stand', '##off', 'over', 'ukraine', ',', 'even', 'as', 'they', 'reported', 'no', 'breakthrough', '##s', 'in', 'high', '-', 'stakes', 'talks', 'on', 'friday', 'aimed', 'at', 'preventing', 'a', 'feared', 'russian', 'invasion']

이와 같이 사용되는 모델에 따라 단어 맥락이 달라질 수도 있다.

토큰 추가와 임베딩 공간
BERT를 이용해 토큰 사전과 임베딩을 실습해본다.
일반적으로 허깅페이스 라이브러리에서 LLM모델에 대한 토큰 추가가 가능한 tokenizer를 제공해 준다. 토큰을 추가하면, 임베딩 차원에 영향을 주므로, 해당 크기를 수정해야 한다. 다음은 이를 고려한 코드를 보여준다. 
from transformers import AutoTokenizer, AutoModelForCausalLM

# 사전 학습모델 및 토크나이저 로딩
model = AutoModelForCausalLM.from_pretrained('model-name')
tokenizer = AutoTokenizer.from_pretrained('model-name')

# 토큰 추가
new_tokens = ['newword1', 'newword2']
tokenizer.add_tokens(new_tokens)

# 임베딩 공간 리사이즈 
model.resize_token_embeddings(len(tokenizer))

# 추가된 토큰과 함께 파인튜닝. 
# (fine-tuning code here)

BPE(Byte Pair Encoding) 토큰 압축
BPE는 바이트 쌍 인코딩(Byte pair Encoding)을 의미하며, 데이터의 가장 일반적인 연속 바이트 쌍을 해당 데이터 내에 발생하지 않는 바이트로 대체하는 데이터 압축 형태이다. 결과 데이터에 대해 프로세스가 반복적으로 반복된다. 자연어 처리(NLP) 및 기계 학습의 맥락에서 BPE는 하위 단어 토큰화 방법으로 사용된다. 

단어를 보다 관리하기 쉬운 하위 단어나 기호로 분할하여 대규모 어휘를 효율적으로 인코딩할 수 있다. 이 접근 방식은 어휘의 크기를 크게 줄이고 희귀 단어나 OOV(어휘에서 벗어난) 용어를 처리하는 모델의 능력을 향상시킬 수 있다.

NLP에 BPE를 적용하는 기본 단계는 다음과 같다.
  • 텍스트를 단어로 나눈 다음 문자로 나누고 각 문자(또는 문자 시퀀스)의 빈도를 계산한다.
  • 인접한 문자 또는 문자 시퀀스의 가장 빈번한 쌍을 반복적으로 찾아, 이를 새로운 문자 시퀀스로 병합한다.
  • 원하는 어휘 크기에 도달할 때까지 또는 더 이상 병합으로 인해 어휘 크기가 크게 줄어들 때까지 병합 프로세스를 반복한다.
BPE는 언어 모델링, 텍스트 생성, 특히 BERT(Bidirection Encoder Representations from Transformers)와 같은 변환기 기반 모델과 같은 다양한 NLP 모델 및 작업에 널리 채택되어 광범위한 어휘를 효율적으로 처리하는 데 도움이 된다.

다음은 그 예제를 보여준다.
from tokenizers import Tokenizer, models, pre_tokenizers, trainers

tokenizer = Tokenizer(models.BPE()) # 토큰화 얻기

tokenizer.pre_tokenizer = pre_tokenizers.Whitespace() # 사용자 토큰 처리 객체
def custom_pre_tokenizer(sequence): # 사용자 토큰 정의
    # Define rules to combine tokens, e.g., "new word" -> "newword"
    combined_sequence = sequence.replace("new word", "newword")
    return combined_sequence

# 토큰 훈련. custom pre-tokenizer 활용함.
trainer = trainers.BpeTrainer()
tokenizer.train(files=["path/to/training/data.txt"], trainer=trainer, pre_tokenizer=custom_pre_tokenizer)

# 훈련된 토큰 저장
tokenizer.save("path/to/customized_tokenizer.json")

임베딩 모델 파인튜닝
다음은 토큰을 추가하고, 임베딩 모델을 파인튜닝하는 보여준다.

from transformers import BertTokenizerFast, BertModel
import torch
from torch import nn

# BERT 토크나이저 사전학습모델 로딩
tokenizer = BertTokenizerFast.from_pretrained('bert-base-uncased')
print(tokenizer.tokenize("[CLS] Hello world, how are you?"))

print(tokenizer.tokenize("[newtoken] Hello world, how are you?"))
tokenizer.add_tokens(['[newtoken]'])

다음과 같이, [newtoken] 토큰 추가 전 테스트. 토큰이 한단어가 아닌 분할 출력된 것 확인
['[',
 'newt',
 '##oke',
 '##n',
 ']',
 'hello',
 'world',
 ',',
 'how',
 'are',
 'you',
 '?']

토큰을 추가하고 다시 토큰화를 한다.
tokenizer.add_tokens(['[newtoken]'])
tokenizer.tokenize("[newtoken] Hello world, how are you?")

제대로 토큰화가 된다. 
['[newtoken]', 'hello', 'world', ',', 'how', 'are', 'you', '?']

토큰값을 확인해 본다.
tokenized = tokenizer("[newtoken] Hello world, how are you?", add_special_tokens=False, return_tensors="pt")
print(tokenized['input_ids'])

tkn = tokenized['input_ids'][0, 0]
print("First token:", tkn)
print("Decoded:", tokenizer.decode(tkn))

다음과 같이, 토큰값이 잘 할당된 것을 알 수 있다.
tensor([[30522,  7592,  2088,  1010,  2129,  2024,  2017,  1029]])
First token: tensor(30522)
Decoded: [newtoken]

임베딩 모델 학습을 위한 BERT 로딩하고, 앞의 토큰 리스트를 모델에 입력한다.
model = BertModel.from_pretrained('bert-base-uncased')
print(model.embeddings)

try:
    out = model(**tokenized)
    out.last_hidden_state
except Exception as e:
    print(e)

임베딩 모델이 추가된 토큰을 학습하지 않았으므로, out of range 에러가 출력될 것이다. 
다음 코드로 BERT 모델의토큰 공간 크기를 확인해 본다.
weights = model.embeddings.word_embeddings.weight.data
print(weights.shape)

출력은 다음과 같이 30522이다.
torch.Size([30522, 768])

이제 [CLS] 토큰을 임베딩 모델에 추가해보자. 
new_weights = torch.cat((weights, weights[101:102]), 0)
new_emb = nn.Embedding.from_pretrained(new_weights, padding_idx=0, freeze=False)
print(new_emb)

다음과 같이 30523으로 토큰 크기가 증가되었다. 
Embedding(30523, 768, padding_idx=0)

새 레이어를 모델 마지막에 추가한다.
model.embeddings.word_embeddings = new_emb
print(model.embeddings)

그 결과로 임베딩 모델의 word_embeddings가 업데이트된다.
BertEmbeddings(
  (word_embeddings): Embedding(30523, 768, padding_idx=0)
  (position_embeddings): Embedding(512, 768)
  (token_type_embeddings): Embedding(2, 768)
  (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
  (dropout): Dropout(p=0.1, inplace=False)
)

앞의 토큰 시퀀스 리스트를 입력한다. 그럼, 제대로 결과가 출력될 것이다.
out = model(**tokenized)
print(out.last_hidden_state)

다음 코드를 실행하면, 추가된 모델이 동일한 결과를 가지는 것을 알 수 있다.
model = BertModel.from_pretrained('bert-base-uncased')
out2 = model(
    **tokenizer("[CLS] Hello world, how are you?", add_special_tokens=False, return_tensors="pt")
)

out3 = torch.all(out.last_hidden_state == out2.last_hidden_state)
print(out3)

결과는 일치하므로 True가 출력 될 것이다.

마무리
LLM 파인튜닝이나 RAG 시 학습 데이터에 포함된 토큰에 대한 적절한 사전이 없으면, 제대로 학습되지 않는다. 이 글은 LLM NLP처리의 핵심인 토큰, 임베딩 모델 파인튜닝에 대한 내용을 간략히 다룬다. 

레퍼런스

2024년 6월 16일 일요일

LLM 의 통계적 패턴 예측성과 한계에 대한 연구

요즘 AI 연구자들이 LLM에 대한 잠재력을 연구하고 있는 분위기가 있는 데, 마침 Google, DeepMind 연구팀으로 부터 관련된 논문이 나왔다. 이 연구는 LLM을 AGI와 같은 Multi function AI로 사용할 수 있는 지에 대한 근본적 성찰과 한계를 밝히는 연구이다.

이와 관련해, github에 해당 코드 등을 공유하고 있다. 
레퍼런스
다음 글은 여러 시사점을 준다. 참고용으로 기록한다. 

추신. 개인적으로 극단을 경계하는 성격이라, LLM이 AGI 도구 보단, 통계적 패턴의 예측 함수로 인식하고 있었는 데, 워낙 너도 나도 LLM으로 다 할 수 있다는 듯 뛰어드는 분위기라... 약장사 구분하기 위해 개인적으로 계속 논문, 소스코드 분석하고, 정리하고 있었다. ChatGPT 충격?에서 2년 가까이 지난 뒤에서야, 나름 국제적 연구진이 연구한 이런 결과들이 나오는 것을 보면, 대부분 어떤 트랜드가 있을 때, 98%는 언론에서 잘 모르고 무책임하게 말하는 것을 따라 휩쓸린다는 것을 한번 더 느낀다. 
이 정도에서 약간 국내 기술 수준도 털리는 느낌인데, 지나서 잠시 생각해 보면, 아무도 그 수준의 연구나 성찰을 화려한 스타군단으로 구성된 AI스타트업, 연구실에서 언급한 적을 논문 언론 등을 통해 한번도 본적이 없었다는 것이다(심지어, 그 스타군단에서 huggingface, langchain처럼 스스로 자랑?하는 성과를 github에서 거의 본 적 없다 보니, 놀랄만한 일이 아니다). 깊게 연구 성찰한 후에야, LLM이 할 수 있는 것들(기능. 무엇은 되고, 어떤것은 안된다 정도)에 대해 자신있게 말할 수 있을 텐데, 이름, 돈, 상황(패닉?)에 눈이 멀어, 깊은 성찰과 연구는 없고, 약장사 만 많은 건은 어쩔 수 없나 보다. 어느 분이 말한 것처럼, 국내 시장의 한계인 듯 하다. 

역사는 반복되는 듯... 인생, 시간이 아까우면, 마음이 급하더라도, 좀 깊게, 하나씩 어떤 물건인지 잘 살펴보는 게 정답이다. 2024.6.6