순수 C 파일 하나로 Llama 2를 학습하고 추론하는 최소한의 구현체입니다.
순수 C로 작은 Llama 2 모델을 추론해 보고 싶었던 적이 있나요? 아니었나요? 그래도 이제 할 수 있습니다!
PyTorch에서 Llama 2 LLM 아키텍처를 학습한 뒤, 간단한 700줄짜리 C 파일 하나(run.c)로 추론하세요. 유용한 일을 하려면 수십억 개 파라미터를 가진 LLM이 필요하다고 생각할 수 있지만, 실제로는 도메인을 충분히 좁히면 아주 작은 LLM도 놀랄 만큼 강력한 성능을 낼 수 있습니다(참조: TinyStories 논문). 이 저장소는 최소주의와 단순성에 초점을 둔 Llama 2 LLM용 ‘풀스택’ 학습 + 추론 솔루션입니다.
아키텍처가 동일하므로 Meta의 Llama 2 모델도 불러와 추론할 수 있습니다. 다만 현재 코드는 fp32 모델만 추론하므로, 7B보다 큰 모델은 실용적으로 불러오기 어려울 가능성이 큽니다. 모델 양자화 작업은 현재 진행 중입니다.
이 저장소는 최근 재미로 진행한 주말 프로젝트에서 시작했다는 점을 알아 두세요. 이전의 nanoGPT를 가져와 GPT-2 대신 Llama-2 아키텍처를 구현하도록 조정했고, 핵심 작업은 run.c에 C 추론 엔진을 작성하는 것이었습니다. 따라서 이 프로젝트는 아직 초기 단계이며 빠르게 변화하고 있습니다. 이 프로젝트에 영감을 준 훌륭한 llama.cpp에 감사드립니다. llama.cpp와 비교해, 아주 단순하고 최소한이며 교육적인 것을 원했기에 Llama 2 아키텍처를 하드코딩하고 의존성 없는 순수 C 추론 파일 하나만 만들기로 했습니다.
먼저 프로젝트를 보관하는 폴더로 이동하여 이 저장소를 해당 폴더에 복제합니다.
git clone https://github.com/karpathy/llama2.c.git
그런 다음 저장소 폴더를 엽니다.
cd llama2.c
이제 작은 Llama 2 모델을 C에서 실행해 봅시다. 모델 체크포인트가 필요합니다. TinyStories 데이터셋에서 학습한 이 15M 파라미터 모델을 내려받으세요(약 60MB 다운로드).
wget https://huggingface.co/karpathy/tinyllamas/resolve/main/stories15M.bin
C 코드를 컴파일하고 실행합니다.
make run
./run stories15M.bin
텍스트 샘플이 스트리밍되는 것을 볼 수 있습니다. 제 M1 MacBook Air에서는 약 110 tokens/s로 실행됩니다. 이를 크게 가속할 수 있는 컴파일 플래그는 성능 또는 Makefile을 참고하세요. 조금 더 큰 42M 파라미터 모델도 시도할 수 있습니다.
wget https://huggingface.co/karpathy/tinyllamas/resolve/main/stories42M.bin
./run stories42M.bin
이 모델도 대화형 속도로 실행되며 더 일관되고 다양한 이야기를 샘플링합니다.
옛날 옛적에 Lily라는 어린 소녀가 있었습니다. 그녀는 침대 위에서 장난감을 가지고 노는 것을 좋아했습니다. 어느 날, 그녀는 봉제 동물들과 티 파티를 열기로 했습니다. 작은 주전자에 차를 따르고 주전자 위에 올려놓았습니다. 갑자기 남동생 Max가 방에 들어와 티 파티에 함께하고 싶어 했습니다. Lily는 차를 나누고 싶지 않아 Max에게 가라고 말했습니다. Max는 울기 시작했고 Lily는 미안해졌습니다. 그녀는 자신의 티 파티를 Max에게 양보하기로 했고 둘은 주전자를 함께 사용했습니다. 그런데 예상치 못한 일이 일어났습니다. 주전자가 흔들리고 꿈틀거리기 시작했습니다. Lily와 Max는 무서웠고 어떻게 해야 할지 몰랐습니다. 갑자기 주전자가 천장을 향해 날아가 침대 위에 내려앉았습니다. Lily와 Max는 놀랐고 서로를 꼭 안았습니다. 그들은 이기적으로 구는 것보다 나누는 일이 훨씬 더 즐겁다는 것을 깨달았습니다. 그날부터 그들은 늘 티 파티와 장난감을 함께 나누었습니다.
접두사나 추가 명령줄 인수로 모델에 프롬프트를 줄 수도 있습니다. 예를 들어 온도 0.8에서 256단계를 샘플링하고 프롬프트를 지정하려면 다음과 같습니다.
./run stories42M.bin -t 0.8 -n 256 -i "One day, Lily met a Shoggoth"
어느 날, Lily는 Shoggoth를 만났습니다. 그는 매우 수줍었지만 아주 너그럽기도 했습니다. Lily가 말했습니다. “안녕 Shoggy! 내가 네 친구가 되어도 될까?” Shoggy는 친구가 생겨 기뻐하며 말했습니다. “그래, 함께 우주를 탐험하자!” 그렇게 그들은 우주를 탐험하는 여행을 떠났습니다. 여행하는 동안 Shoggy는 우주의 모든 멋진 것들을 Lily에게 기쁘게 설명해 주었습니다. 하루가 끝날 무렵 Lily와 Shoggy는 우주에서 많은 멋진 것들을 모았고, 둘 다 무척 자랑스러웠습니다. 그들은 하나의 멋진 짝으로서 우주를 탐험하고 서로에게 결코 너그러움을 멈추지 않겠다고 약속했습니다.
더 뛰어난 110M 파라미터 모델도 उपलब्ध합니다. 모델을 참고하세요.
샘플링에 관한 간단한 참고로, 대략 최상의 결과를 위한 권장 설정은 -t 1.0 -p 0.9, 즉 온도 1.0(기본값)에 top-p 샘플링 0.9(기본값)를 함께 쓰는 것입니다. 직관적으로 top-p는 확률이 아주 작은 토큰이 샘플링되지 않도록 하므로, 샘플링 중 ‘운이 나빠지는’ 일을 막고 이후에 ‘궤도를 이탈할’ 가능성을 낮춥니다. 보다 일반적으로 샘플의 다양성을 제어하려면 온도(-t를 0과 1 사이에서 바꾸고 -p 0으로 top-p를 끔) 또는 top-p 값(-p를 0과 1 사이에서 바꾸고 -t 1 유지) 중 하나를 사용하되, 둘 다 사용하지는 마세요. LLM 샘플링 전략에 관한 좋은 설명은 이 글, 이 글, 이 글에 있습니다.
신경망 아키텍처가 동일하므로 Meta가 공개한 Llama 2 모델도 추론할 수 있습니다. 안타깝게도 라이선스 때문에 약간의 마찰이 있습니다(체크포인트를 직접 올릴 수는 없다고 생각합니다). 따라서 1단계는 Meta 지침을 따라 Llama 2 체크포인트를 받는 것입니다. 체크포인트를 얻었다면 llama2.c 형식으로 변환해야 합니다. 이를 위해 파이썬 의존성을 설치하고(pip install -r requirements.txt) export.py 파일을 사용합니다. 예를 들어 7B 모델은 다음과 같습니다.
python export.py llama2_7b.bin --meta-llama path/to/llama/model/7B
내보내기에는 약 10분 정도가 걸리며, 현재 디렉터리에 llama2_7b.bin이라는 26GB 파일(float32 7B 모델 가중치)을 생성합니다. 노력에도 불구하고 보고된 바가 있습니다. 지금은 두 가지 이유로 7B를 초과하는 모델 실행을 시도하지 않겠습니다. 첫째, 포인터 산술의 정수 오버플로 때문에 현재 13B+는 작동하지 않으며 아직 수정되지 않았습니다. 둘째, 수정되더라도 이 저장소는 현재 float32 추론을 하므로 상당히 쓸 수 없을 정도로 느릴 것입니다. 내보내기가 끝나면 실행할 수 있습니다.
./run llama2_7b.bin
클라우드의 CPU Linux 장비에서 OpenMP로 컴파일하고 96개 스레드를 사용했을 때 약 4 tokens/s로 실행되었습니다. (M1 MacBook Air에서는 현재 make runfast로 빌드하면 토큰당 약 30초에 가깝습니다.) 출력 예시는 다음과 같습니다.
이 문서의 목적은 최근 개발과 상업적으로 사용 중인 기술을 모두 포함하여 CoO 생성 기술의 최신 수준을 조명하는 것입니다. 초점은 미래의 지배적인 공정이 될 가능성이 가장 높고 따라서 S&T ... R&D의 관심 대상이 될 기술에 있습니다. 이에 따라 러시아, 일본 및 유럽에서 개발된 CoO 생성 기술을 어느 정도 깊이 있게 설명합니다. 문서는 복합 제품으로서의 코발트 산화물 소개와 필수 재료인 코발트에 대한 간략한 관점으로 시작합니다. 이어 에너지 및 자본 소비와 환경 피해 측면에서 이용 가능한 CoO 생성 공정을 논의합니다.
기본 모델이라서요... ¯_(ツ)_/¯. 기본 모델을 추론할 수 있으므로 채팅 모델도 아주 쉽게 추론하고 대화할 수 있을 것입니다. 그리고 7B를 더 효율적으로 실행할 방법을 찾으면 학습 스크립트에 LoRA를 추가하고, 이 저장소 안에서 파인튜닝을 마음껏 할 수 있습니다!
Llama Chat 모델과도 대화할 수 있습니다. 위와 정확히 동일하게 채팅 모델을 내보냅니다.
python export.py llama2_7b_chat.bin --meta-llama /path/to/7B-chat
그런 다음 -m 플래그로 채팅 모드를 지정해 대화합니다.
./run llama2_7b_chat.bin -m chat
지원이 아직 불완전하지만 Meta의 Code Llama 모델도 시도할 수 있습니다. 특히 일부 하이퍼파라미터(예: RoPE 레이어의 상수)가 변경되었으므로 추론이 정확하지 않고 현재 약간 버그가 있습니다. 수정 방법을 살펴보고 있습니다. 일반 및 instruct 변형용 토크나이저를 빌드하고 추론 시 전달해야 합니다.
python export.py codellama2_7b.bin --meta-llama /path/to/CodeLlama-7b
python tokenizer.py --tokenizer-model=/path/to/CodeLlama-7b/tokenizer.model
./run codellama2_7b.bin -z /path/to/CodeLlama-7b/tokenizer.bin
Code Llama Instruct와 대화하기:
python export.py codellama2_7b_instruct.bin --meta-llama /path/to/CodeLlama-7b-Instruct
python tokenizer.py --tokenizer-model=/path/to/CodeLlama-7b-Instruct/tokenizer.model
./run codellama2_7b_instruct.bin -m chat -z /path/to/CodeLlama-7b-Instruct/tokenizer.bin
위의 (기본) 스크립트 run.c는 순전파 전체 계산을 fp32로 유지하는 float32 순전파를 사용합니다. 참조 코드로서는 이해하기 매우 쉽지만, 모델 체크포인트 파일이 매우 크고(각 개별 가중치마다 4바이트 필요) 순전파가 상대적으로 느리다는 단점이 있습니다. 실제로 널리 쓰이는 추론 최적화는 모델 파라미터를 더 낮은 정밀도로 양자화하는 것입니다. 약간의 정확도를 포기하는 대신 체크포인트 크기를 줄이고 순전파를 빠르게 합니다(추론 대부분이 정수 산술을 사용하기 때문입니다). 경험적으로 LLM은 4비트(혹은 그보다 더 낮은) 정밀도도 견딜 수 있지만, 여기서는 이점을 얻으면서 모델 정확도를 너무 많이 희생하지 않는 ‘안전한’ 설정인 int8을 사용합니다. matmul에 참여하는 가중치만 양자화합니다. 다른 모든 파라미터(특히 RMSNorm의 스케일과 편향)는 이 레이어들이 매우 민감하므로 float32로 유지합니다. 체크포인트 크기 축소만 원한다면 가중치를 양자화해 체크포인트로 저장한 뒤 run.c에서 역양자화하고, 평소처럼 float32 추론을 수행해도 됩니다. 완전히 괜찮습니다. 하지만 여기서는 한 단계 더 나아가(표준 관행처럼) 순전파의 활성화도 추가로 양자화합니다. 그러려면 런타임에 float32와 int8 사이에서 동적으로 양자화 및 역양자화를 해야 하므로 오버헤드가 생깁니다. 그러나 이제 계산 대부분, 특히 matmul이 순수 정수 산술을 사용하며 가중치와 활성화 모두 int8로 들어간다는 이점이 있습니다. 여기서 근본적인 속도 향상이 가능해집니다. 사용하는 방식은 “Q8_0” 양자화(llama.cpp 용어)이며, 0은 가중치 양자화가 0을 중심으로 대칭이고 범위 [-127, 127]로 양자화됨을 뜻합니다.
양자화된 순전파는 runq.c에 구현되어 있습니다. 사용하려면 양자화 형식으로 모델을 내보내야 합니다. 예를 들어 Llama 2 7B의 float32 버전은 다음처럼 내보냈습니다.
python export.py llama2_7b.bin --meta-llama path/to/llama/model/7B
7B 파라미터 각각이 4바이트(fp32)이므로 26GB 파일이 생성됩니다. 양자화하여 내보내려면 대신 버전 2 내보내기를 사용합니다.
python export.py llama2_7b_q80.bin --version 2 --meta-llama path/to/llama/model/7B
몇 분 동안 실행되지만 이제 6.7GB 파일만 생성합니다. Meta가 아닌 체크포인트를 내보낼 때는 --meta-llama 인수 대신 --checkpoint 인수를 사용합니다(이에 관한 자세한 문서는 아래에 있습니다). 이제 추론해 봅시다. 큰 모델이므로 OMP를 쓰는 편을 좋아하며, 예를 들어 제 Linux 장비에서는 다음과 같습니다.
make runomp
OMP_NUM_THREADS=64 ./run llama2_7b.bin -n 40
OMP_NUM_THREADS=64 ./runq llama2_7b_q80.bin -n 40
타이밍을 얻기 위해 40단계를 실행합니다. 제 환경에서 float32 버전은 4.6 tok/s, int8 버전은 14 tok/s로 실행됩니다. 따라서 체크포인트 크기를 4배 줄이면서 3배 속도 향상을 달성했습니다. 단, 순전파가 int8로 양자화되므로 품질은 눈에 띄지 않을 정도로 아주 약간 낮습니다.
Llama 2 아키텍처를 사용하는 모든 huggingface 모델을 불러올 수 있습니다. 모델 .bin 파일을 내보내려면 export.py 스크립트와 --hf 플래그를 참고하세요.
더 작은 처음부터 학습한 모델의 예시를 위해 TinyStories에서 소형 모델 시리즈를 학습했습니다. 이들은 모두 제 학습 환경(4X A100 40GB GPU)에서 몇 시간 안에 학습되었습니다. 110M은 약 24시간이 걸렸습니다. 원본 PyTorch .pt와 llama2.c 형식 .bin 모두를 huggingface hub tinyllamas에 호스팅하고 있습니다.
| 모델 | dim | n_layers | n_heads | n_kv_heads | 최대 컨텍스트 길이 | 파라미터 | 검증 손실 | 다운로드 |
|---|---|---|---|---|---|---|---|---|
| 260K | 64 | 5 | 8 | 4 | 512 | 260K | 1.297 | stories260K |
| OG | 288 | 6 | 6 | 6 | 256 | 15M | 1.072 | stories15M.bin |
| 42M | 512 | 8 | 8 | 8 | 1024 | 42M | 0.847 | stories42M.bin |
| 110M | 768 | 12 | 12 | 12 | 1024 | 110M | 0.760 | stories110M.bin |
110M 모델은 크기 면에서 GPT-1과 동등합니다. 또는 최대 컨텍스트 길이가 2048이 아니라 1024라는 점을 제외하면 GPT-2 시리즈 중 가장 작은 모델(GPT-2 small)과도 같습니다. GPT-1/2 아키텍처와의 주목할 만한 차이는 Llama가 절대/학습형 위치 임베딩 대신 RoPE 상대 위치 임베딩을 사용하고, MLP에 좀 더 정교한 SwiGLU 비선형성을 사용하며, LayerNorm 대신 RMSNorm을 사용하고, 모든 Linear 레이어에서 bias=False를 사용하며, 선택적으로 멀티쿼리를 사용한다는 점뿐입니다.
이 저장소의 코드로 작은 Llama 2를 처음부터 학습하는 방법을 살펴봅시다. 먼저 소스 데이터셋을 내려받고 사전 토큰화합니다. 예를 들어 저는 TinyStories를 좋아하므로 현재 이 저장소에서 제공되는 유일한 예시입니다. 하지만 코드를 보면 데이터셋 추가는 아주 쉬울 것입니다.
python tinystories.py download
python tinystories.py pretokenize
그런 다음 모델을 학습합니다.
python train.py
간략한 학습 안내. 더 특이한 실행 방법과 하이퍼파라미터 재정의는 train.py 스크립트를 참고하세요. 파라미터 설정 방법을 간략히 안내합니다. Chinchilla 논문 맨 끝 표를 보면 Transformer 파라미터(dim, n_layers, n_heads)가 함께 커지거나 작아지는 방식을 알 수 있습니다. 이 패턴을 외삽/보간하여 더 크거나 작은 transformer를 만드세요. 문제에 따라 원하는 최대 컨텍스트 길이를 설정하세요. 이는 다음 토큰을 예측할 때 중요한 최대 토큰 수여야 합니다. 예를 들어 Llama 2는 2048을 사용합니다. 다음으로 업데이트당 총 배치 크기(스크립트가 “tokens per iteration will be:”로 출력함)를 중간 규모 애플리케이션에서는 약 100K 토큰으로 설정하는 것이 좋습니다. 작은 애플리케이션에서는 더 낮아도 되고, 대규모 학습(예: GPT/LLama)에서는 보통 약 0.5M 또는 그 이상입니다. 먼저 시스템이 허용하는 만큼 batch_size를 최대로 설정하고(예: 최근 실행에서는 그보다 높이면 GPU 메모리가 부족했기에 16이었습니다), 총 배치 크기 약 100K에 도달하도록 gradient_accumulation_steps를 필요한 만큼 높입니다. 마지막으로 learning_rate(LR)를 조정합니다. 학습이 허용하는 한 높게 설정해야 합니다. 아주 작은 네트워크는 큰 LR(예: 1e-3 또는 더 높게)을 사용할 수 있습니다. 큰 네트워크는 더 낮은 LR이 필요합니다. 3e-4는 대부분 중간 규모 애플리케이션에서 안전한 선택이지만 작은 네트워크에는 너무 낮을 수 있으니 높여 보세요! 마지막으로 max_iters는 학습 길이입니다. 여러 설정을 시도하세요. 저는 대체로 이 파라미터들만 조정하고 나머지는 대부분 그대로 둡니다. 최적과는 거리가 멀다고 생각하지만, 110M 모델을 학습한 예시는 다음과 같습니다. dim 768, n_layers 12, n_heads 12(각 헤드 크기는 768 / 12 = 64 채널), 시퀀스 길이 1024, 배치 크기 16(A100 40GB GPU에 들어가는 최대치), 총 토큰 배치 크기를 업데이트당 16 배치 크기 * 시퀀스의 1024 토큰 * 8 grad_accum = 131,072 토큰으로 만들기 위해 gradient_accumulation_steps = 8이 필요했습니다. 좋습니다. 학습률은 4e-4(아마 조금 너무 낮음), max_iters는 200K(아마 조금 너무 높음)였습니다. 중간 크기에서는 보통 약간 도움이 되므로 dropout은 0.1이었습니다. 그게 전부였습니다. 클라우드 장비의 GPU 4개에서 Distributed Data Parallel(DDP)을 사용해 실행했으며 학습에는 약 하루가 걸렸습니다.
모델 학습을 건너뛰고 싶은 마음은 충분히 이해합니다. 간단한 데모라면 사전 학습 모델 중 하나를 내려받으세요(모델 섹션 참고). 예를 들면 다음과 같습니다.
wget https://huggingface.co/karpathy/tinyllamas/resolve/main/stories15M.bin
model.bin 파일이 있으면 C에서 추론할 수 있습니다. 먼저 C 코드를 컴파일하세요.
make run
이제 간단히 실행할 수 있습니다.
./run stories15M.bin
토큰이 스트리밍되는 모습을 보세요. 재미있습니다! 비교를 위해 PyTorch 추론 스크립트도 실행할 수 있습니다. huggingface hub에서 모델 하나를 다시 내려받고 sample.py 스크립트가 이를 가리키게 합니다.
wget https://huggingface.co/karpathy/tinyllamas/resolve/main/stories15M.pt -P out15M
python sample.py --checkpoint=out15M/stories15M.pt
동일한 결과를 제공합니다.
위의 모든 내용에서는 32,000개 토큰을 가진 사용자 지정 Lllama 2 토크나이저를 가정했습니다. 하지만 많은 소규모 LLM에서는 이렇게 큰 어휘가 과할 수 있습니다. 작은 애플리케이션을 염두에 두고 있다면 직접 토크나이저를 학습하는 편이 훨씬 나을 수 있습니다. 이렇게 하면 모든 것이 더 좋아질 수 있습니다. 더 작은 어휘를 쓰면 모델 파라미터가 줄고(토큰 임베딩 테이블이 훨씬 작아짐), 추론이 빨라지며(예측할 토큰이 줄어듦), 예제당 평균 시퀀스 길이도 더 짧아질 수 있습니다(데이터에서 압축 효율이 훨씬 좋아짐). 그러니 사용자 지정 토크나이저를 학습하는 방법을 봅시다.
기본적으로 tinystories 데이터셋을 사전 토큰화하려면 다음을 순서대로 실행해야 했습니다.
python tinystories.py download
python tinystories.py pretokenize
여기서 pretokenize 단계는 Llama 2 토크나이저(어휘 크기 32,000)를 불러와 내려받은 텍스트를 정수로 변환하고 파일에 저장합니다. 이제 예시로 4096토큰 토크나이저를 학습하도록 다음과 같이 바꿉니다.
python tinystories.py download
python tinystories.py train_vocab --vocab_size=4096
python tinystories.py pretokenize --vocab_size=4096
train_vocab 단계는 sentencepiece 라이브러리를 호출해 토크나이저를 학습하고 새 파일 data/tok4096.model에 저장합니다. Meta가 어휘를 학습할 때 사용했다고 생각되는 설정을 최대한 재현하려 했습니다. 이 방식은 텍스트 데이터의 원시 utf8 바이트 시퀀스에서 시작해, 가장 흔하게 연속으로 나타나는 토큰 쌍을 반복적으로 병합하여 어휘를 만드는 Byte Pair Encoding 알고리즘을 사용합니다. tinystories.py 파일을 살펴보세요. 사용자 지정 토크나이저는 어휘 크기로 색인된 특별한 디렉터리 구조에 저장됩니다.
흥미로운 짧은 참고로, tinystories에 특화해 학습한 4096 크기 어휘는 기본 Llama 2 토크나이저의 32000 토큰과 예제당 시퀀스 길이가 거의 같습니다! 이는 사용자 지정 맞춤형 토크나이저가 특정 텍스트에 훨씬 더 잘 적응하여 매우 효과적으로 압축할 수 있음을 뜻합니다. 따라서 학습된 모델은 더 작고 빨라집니다.
이제 사용자 지정 토크나이저로 데이터셋을 사전 토큰화했으므로 모델을 학습할 수 있습니다. 학습 스크립트 train.py는 정확한 토큰에는 관심이 없고, 모델을 올바르게 초기화할 수 있도록 어휘 크기만 필요로 합니다. 따라서 모델을 학습할 때 다음을 반드시 전달하세요.
python train.py --vocab_source=custom --vocab_size=4096
(기본값은 각각 llama2와 32000이며, 기본 Llama 2 토크나이저를 뜻합니다.) 이로써 모델이 학습됩니다. 마지막으로 run.c 스크립트로 추론할 준비가 되었습니다. 이를 위해 두 가지가 필요합니다. 첫째, 토크나이저를 .bin 형식으로 내보내야 합니다.
python tokenizer.py --tokenizer-model=data/tok4096.model
이 명령은 토크나이저를 data/tok4096.bin에 씁니다. 이제 -z 플래그로 이 토크나이저를 가리켜 추론을 실행할 수 있습니다.
./run out/model.bin -z data/tok4096.bin
샘플이 출력되어야 합니다. -z 플래그를 생략하면 기본 Llama 2 토크나이저를 사용합니다. 그러면 정수 시퀀스 자체는 잘 생성되지만, 다른 어휘를 사용해 텍스트로 변환되므로 횡설수설한 것처럼 보입니다.
시스템에 따라 이 코드를 잠재적으로 가속하는 방법은 많습니다. 많은 참고 사항이 있는 Makefile을 살펴보세요. make run 명령은 현재 기본적으로 -O3 최적화를 사용합니다.
gcc -O3 -o run run.c -lm
-O3에는 컴파일 시간과 메모리 사용량 측면에서 비용이 큰 최적화가 포함됩니다. 벡터화, 루프 펼치기, 분기 예측 등이 이에 해당합니다.
훨씬 더 좋은 성능을 얻으려면 make runfast로 컴파일해 보세요. 이는 -O3에 더해 C/IEEE 명세 준수를 깨뜨릴 수 있는 추가 최적화를 포함하는 -Ofast 플래그를 활성화합니다. 자세한 내용은 GCC 문서를 참고하세요.
더 일반적인 CPU가 아니라 컴파일하는 장비의 아키텍처를 사용하도록 프로그램을 컴파일하려면 -march=native를 시도하세요. 개선된 벡터 명령/너비와 같은 추가 최적화 및 하드웨어별 튜닝을 활성화할 수 있습니다.
지금까지 M1 MacBook Air에서 본 가장 빠른 처리량은 make runfast를 사용했을 때였습니다.
gcc를 clang으로 교체하는 실험도 할 수 있습니다.
gcc로 컴파일한다면 -funroll-all-loops도 실험해 보세요. PR #183을 참고하세요.
OpenMP. OpenMP로 컴파일하면 큰 개선을 얻을 수도 있습니다. 이는 matmul과 attention 내부의 #pragma omp parallel for를 ‘활성화’하여 루프 작업을 여러 프로세서에 나누도록 합니다. 먼저 OpenMP 라이브러리와 clang 컴파일러를 설치해야 합니다(예: ubuntu에서는 apt install clang libomp-dev). 이후 다음을 수행하는 make runomp로 컴파일할 수 있습니다.
clang -Ofast -fopenmp -march=native run.c -lm -o run
추론을 실행할 때는 스레드 수를 설정하기 위해 OpenMP 플래그를 사용하세요.
OMP_NUM_THREADS=4 ./run out/model.bin
시스템 리소스에 따라 이 하이퍼파라미터를 조정하고 더 많은 스레드를 사용할 수 있습니다. 하지만 많다고 항상 좋은 것은 아니며, 보통 약간 U자형입니다. 특히 CPU에 SMT(멀티스레딩)가 있다면 논리 코어 수보다 물리 코어 수로 스레드 수를 설정해 보세요. 캐시 충돌과 통신 오버헤드 때문에 성능 차이가 클 수 있습니다. PyTorch 문서의 CPU별 최적화에 여기에도 적용되는 좋은 정보가 있습니다.
Windows에서는 Visual Studio 명령 프롬프트에서 build_msvc.bat를 사용해 msvc로 빌드하거나, linux 또는 windows의 mingw 컴파일러 도구 체인으로 windows 대상을 빌드하려면 make win64를 사용할 수 있습니다. OMP_NUM_THREADS 환경 변수를 설정하지 않는 한 MSVC 빌드는 자동으로 openmp와 CPU에 적합한 최대 스레드 수를 사용합니다.
Centos 7, Amazon Linux 2018에서는 rungnu Makefile 대상을 사용하세요. openmp를 사용하려면 make rungnu 또는 make runompgnu를 사용합니다.
Mac에서는 openmp 빌드에 brew의 clang을 사용하세요. brew install llvm로 clang을 설치하고, 설치된 clang 바이너리로 openmp를 사용해 컴파일합니다. make runomp CC=/opt/homebrew/opt/llvm/bin/clang
$ pip install pytest
$ pytest
현재는 test_all.py 내부의 두 테스트를 호출합니다. 두 테스트는 C와 Python에서 각각 200단계 동안 모델을 순전파하고, 출력을 알려진 정상 기대 출력과 비교합니다. 테스트는 현재 단 몇 초 만에 실행되지만, 임시 test 디렉터리에 stories260K 모델을 내려받아 캐시해야 합니다(다운로드는 약 2MB뿐입니다).
test.c 파일에는 C 테스트도 있습니다. make testcc로 실행하거나, 더 많은 출력 내용을 보려면 다음을 사용하세요.
make testcc VERBOSITY=1
도움 요청: 더 많은 테스트 추가를 도와주세요.
훌륭한 Lambda labs가 친절하게 제공한 4X A100 40GB 장비에서 llama2.c 이야기 생성 모델을 학습했습니다. 감사합니다.
기존 discord 채널(zero to hero youtube series에 사용하는 채널)을 재사용할 수 있겠다고 생각했습니다. 간단한 질문, 관련 논의 등은 discord의 #llama2c 채널을 참고하세요.
이 저장소와 받아들여질 가능성이 높은 PR 종류에 관한 몇 마디입니다. 이 저장소의 목표는 무엇일까요? 기본적으로 매우 다양한 애플리케이션에서 사용자 지정 마이크로 LLM(대략 100M - 대략 1B 파라미터, 최대 대략 10B 파라미터라고 합시다)을 학습하거나 파인튜닝하고, 이를 엣지에 가까운 환경(MCU, 휴대폰, 웹 브라우저, 노트북 등)에 배포하는 데 큰 관심이 생길 것이라 생각합니다. 이 저장소가 학습과 추론 모두에서 이 워크플로를 지원하는 가장 단순하고, 가장 작고, 가장 해킹하기 쉬운 저장소가 되기를 바랍니다. 특히 이 저장소는 수백 파일로 이루어진 중첩 디렉터리 구조 전반에 걸친 난해한 코드를 제어하는 1000개의 노브가 있는 복잡한 프레임워크가 아닙니다. 대신 대부분의 애플리케이션은 이 저장소를 포크하고 특정 요구 및 배포 플랫폼에 맞춰 수정하고 싶어 할 것이라 예상합니다.
배포 효율성을 무엇보다 중시하는 사람은 llama.cpp를 살펴봐야 합니다. 이 저장소도 효율성을 중요하게 여기지만 단순성, 가독성 또는 이식성을 희생하지는 않습니다. 기본적으로 많은 사람이 학습 코드가 읽기 쉬운 .py 파일 2개이고 추론 코드가 C 500줄이기 때문에 이 저장소를 찾는다고 생각합니다. 그러므로 별도 포크에서 사람들이 흥미를 느끼는 어떤 다운스트림 애플리케이션으로든 쉽게 수정할 수 있는 가장 단순한 ‘참조 구현’의 일종으로 계속 남기를 바랍니다. 모든 기능을 갖출 필요는 없습니다. 100가지 다른 옵션이나 설정을 받아들일 필요도 없습니다. 가장 효율적일 필요도 없습니다. 몇 가지 예시는 다음과 같습니다.
pragma omp parallel for를 추가함 => 즉시 병합.아주 적합하지 않은 PR의 예시는 다음과 같습니다.
후보 PR에 이런 요소가 있다고 해서 병합되지 않는다는 뜻은 아니며, 다만 회색 영역에 들어간다는 뜻입니다. 요약: 해킹 가능성과 가독성을 유지하면서 저장소의 효율성과 이식성을 향상하는, 대체로 작고 대체로 국소적이며 널리 적용 가능하고 깔끔한 변경이라면 기꺼이 병합하겠습니다. 프로젝트 개선을 돕고자 하는 모든 PR에 감사드립니다! <3.
Rust
Go
Android
C
C++
JavaScript
Zig
Julia
Scala
Java
Kotlin
Python
C#
F#
Dart
Web
WebAssembly
Fortran
Mojo
OCaml
Hare
llama2.c - Llama 2 Everywhere by @trholding: 독립 실행형, 부팅 가능 및 이식 가능한 바이너리 Llama 2
llama2.c-zh - 이중 언어 중국어 및 영어 by @chenyangMl: 중국어와 영어 모두에서 학습 및 추론을 지원하도록 토크나이저 확장
Haskell