2024년 3월 16일 토요일

GPU 없는 로컬에서 서비스 가능한 경량 소형 LLM, LLAMA2.c 빌드, 실행, 학습 및 코드 분석하기

이 글은 GPU 없는 컴퓨터에서 경량 LLAMA2.c (라마씨)를 빌드하고, 로컬에서 실행, 학습하는 방법을 간략히 따라해 본다.
LLAMA2.c

머리말
앞서 설명한 코드 라마2는 최소 28GB이상의 GPU RAM이 필요하다. 많은 개발자의 경우, 현재 시점에서 이 정도 수준의 GPU 스펙 장비를 가진 경우는 매우 드물다. CoLab의 경우 T4 GPU는 16GB 크기를 가진다. 그러므로, 이를 해결할 수 있는 솔류션이 개발되었다. 

LLAMA2.c는 OpenAI 엔지니어인 Andrej Karpathy가 개발한 라마2를 이용한 소형 LLM 오픈소스 프로젝트이다. 이 라마 코드는 순수 C로 개발되었으며, 모델은 겨우 100MB로 동작되어, GPU가 없는 로컬 PC에서도 가볍게 실행된다. 

LLAMA.c 설치
설치, 실행 및 학습 방법은 다음과 같다. 터미널에서 다음 명령을 실행한다.
cd llama2.c
wget https://huggingface.co/karpathy/tinyllamas/resolve/main/stories15M.bin

이 결과, https://huggingface.co/datasets/roneneldan/TinyStories 으로 학습된 모델이 다운로드된다.

실행하기
코드를 컴파일하고, 다운로드받은 스토리 학습 모델을 실행한다. 
make run
./run stories15M.bin

실행 결과는 다음과 같다. 텍스트가 잘 생성되는 것을 확인할 수 있다.

좀 더 큰 모델을 다운받아 실행해 본다.
wget https://huggingface.co/karpathy/tinyllamas/resolve/main/stories42M.bin
./run stories42M.bin
./run stories42M.bin -t 0.8 -n 256 -i "One day, Lily met a Shoggoth"


다음과 같이, 접두어, 추가 명령줄 인수를 지정할 수도 있다.
-t: 생성 온도. 0~1.0
 
데이터 학습 방법
새로운 데이터 훈련을 위해서는 앞에서 사용한 스토리 모델이 어떻게 학습되었는지 확인해 볼 필요가 있다. 그 방법은 다음과 같다.
python tinystories.py download
python tinystories.py pretokenize
python train.py

학습 데이터 다운로드 화면 일부

이제 컴파일을 한다. 
make run

다음과 같이 실행하면 된다. 
./run stories15M.bin

코드 및 학습 데이터 분석하기
LLAMA2.c의 구조는 매우 간단한데, make파일을 분석해 보면 단 하나의 run.c를 컴파일해 실행파일을 빌드하는 것을 확인할 수 있다.
gcc -O3 -o run run.c -lm

run.c의 주요 부분을 살펴보면 다음과 같다. 
typedef struct {
    // token embedding table
    float* token_embedding_table;    // (vocab_size, dim)
    // weights for rmsnorms
    float* rms_att_weight; // (layer, dim) rmsnorm weights
    float* rms_ffn_weight; // (layer, dim)
    // weights for matmuls. note dim == n_heads * head_size
    float* wq; // (layer, dim, n_heads * head_size)
    float* wk; // (layer, dim, n_kv_heads * head_size)
    float* wv; // (layer, dim, n_kv_heads * head_size)
    float* wo; // (layer, n_heads * head_size, dim)
    // weights for ffn
    float* w1; // (layer, hidden_dim, dim)
    float* w2; // (layer, dim, hidden_dim)
    float* w3; // (layer, hidden_dim, dim)
    // final rmsnorm
    float* rms_final_weight; // (dim,)
    // (optional) classifier weights for the logits, on the last layer
    float* wcls;
} TransformerWeights;

이 구조체의 이름은 TransformerWeights이며, 이는 Transformer 모델의 가중치를 저장하는 데 사용된다. 트랜스포머의 어텐션 스코어를 계산하는 데 핵심적인 자료구조이다. 각 필드는 다음과 같은 의미를 가진다:
  • token_embedding_table: 토큰 임베딩 테이블을 나타낸다. 이는 어휘 크기(vocab_size)와 차원(dim)의 2차원 배열이다.
  • rms_att_weight, rms_ffn_weight: RMSNorm의 가중치를 나타낸다. 각각 attention과 feed-forward network에 대한 것이다.
  • wq, wk, wv, wo: 각각 query, key, value, output에 대한 가중치를 나타낸다. 이들은 multi-head attention에서 사용된다.
  • w1, w2, w3: feed-forward network에서 사용되는 가중치이다.
  • rms_final_weight: 최종 RMSNorm의 가중치를 나타낸다.
  • wcls: (선택적으로) 마지막 레이어에서 로짓에 대한 분류기 가중치를 나타낸다.
  • 이 구조체는 Transformer 모델의 가중치를 저장하고 관리하는 데 사용된다.
전체적인 프로그램 실행 구조는 다음과 같다. 

1. 이 코드는 트랜스포머 모델의 구성, 가중치 및 상태에 대한 구조와 메모리 할당, 메모리 해제 및 체크포인트 읽기 기능을 정의한다.
2. 'rmsnorm', 'softmax', 'matmul'과 같은 신경망 블록을 위한 함수도 포함되어 있다.
3. 코드는 Transformer 모델을 통해 입력 토큰을 처리하여 출력 로짓을 생성하는 'forward' 함수를 구현한다.
4. 또한 BPE(Byte Pair Encoding) 토크나이저가 문자열을 토큰으로 또는 그 반대로 변환하는 구조와 함수가 있다.
5. 코드에는 argmax, 샘플링 및 top-p 샘플링과 같은 다양한 전략을 사용하여 확률을 기반으로 토큰을 샘플링하는 '샘플러' 구조와 함수가 포함된다.
6. 시간 측정을 위한 유틸리티 기능과 Transformer 모델을 기반으로 텍스트를 생성하는 '생성' 기능을 정의한다.
7. 마지막으로 트랜스포머 모델을 사용하여 사용자와 어시스턴트 간의 대화를 시뮬레이션하는 '채팅' 기능을 정의한다.  
전체 프로그램 구조도

앞에서 사용된 1.5GB가 넘는 스토리 학습 데이터의 구조는 다음과 같이 되어 있다.
학습 데이터 구조 일부

이를 본인의 목적에 맞게 적절히 데이터를 준비한 후, 다음과 같이 동일한 방식으로 학습하면 가중치 벡터가 저장된 bin파일을 얻을 수 있다. 이를 이용해, 앞서 언급된 방식과 동일하게 사용하면 된다.

레퍼런스

댓글 없음:

댓글 쓰기