1000行C語言搓出GPT-2!AI大神Karpathy新項目剛上線就狂攬2.5k星
斷更近一個月,Karpathy終于上線了。
這次不是AI大課,而是帶來一個新項目。
僅用1000行純C語言訓完GPT-2。
想象一下,如果我們能夠不依賴于龐大的PyTorch(245MB)和cPython(107MB)庫,僅僅使用純C語言就能訓練大型語言模型(LLM),那會怎樣?
現在,借助llm.c,這件聽起來似乎不太可能的事,已經成為了現實!
這個項目的亮點在于,它僅用約1000行簡潔的C代碼,就實現了在普通計算機處理器(CPU)上訓練GPT-2模型的能力。
而且,這份代碼不僅可以立即編譯運行,其訓練結果也和PyTorch版本的GPT-2完全一致。
之所以選擇GPT-2作為起點,是因為它標志著大型語言模型發展史上的一個重要里程碑,是第一次以我們現在所熟悉的形式整合了這樣的技術棧,并且模型權重也是公開可獲取的。
這一項目剛剛發布幾個小時,已經獲得了2.5k星。
項目地址:??https://github.com/karpathy/llm.c??
有網友表示,初創公司正在等著Karpathy挖掘新的點子。
很少有人知道,SUNO一開始是nanoGPT的一個分支。(Suno創業團隊首款產品Bark受到了nanoGPT的啟發)
或許Karpathy正在嘗試的是重新設計LLM架構,通過llm.c項目去探索一種更簡單、高效的模型訓練方法。
「我無法創造的,我就無法理解」。
Karpathy完全讓AI走向大眾化。
那么,僅用C語言如何訓出LLM?
千行C代碼訓完GPT-2
項目開篇介紹中,Karpathy還提到了自己目前正在進行的研究:
- 直接使用CUDA實現,速度會快得多,可能接近PyTorch。
- 使用SIMD指令加速CPU版本,x86上的AVX2/ARM上的NEON(比如蘋果芯片)。
- 采用更現代的架構,如Llama2、Gema等。
對于repo,Karpathy希望同時維護干凈、簡單的參考實現以及更優化的版本,這些版本可以接近PyTorch,但只需很少的代碼和依賴項。
快速入門
下載數據集,并將其進行分詞。Tinyshakepeare數據集下載和分詞速度最快:
python prepro_tinyshakespeare.py
打印內容如下:
Saved 32768 tokens to data/tiny_shakespeare_val.bin
Saved 305260 tokens to data/tiny_shakespeare_train.bin
其中,.bin文件包含有int32的原始數據流,這些整數代表了通過GPT-2分詞器定義的Token ID。
當然,也可以通過運行prepro_tinystories.py來對TinyStories數據集進行分詞處理。
理論上講,現在已經能夠開始訓練模型了。但是,目前基于CPU和FP32的參考代碼運行效率極低,無法從零開始訓練這些模型。
因此,我們選擇先用OpenAI發布的GPT-2模型權重進行初始化,再對模型進行微調。
為了這個目的,我們需要下載GPT-2模型的權重文件,并把它們作為檢查點保存下來,這樣就可以在C語言環境中進行加載了:
python train_gpt2.py
這個腳本的作用是下載GPT-2(124M)模型,并對單個數據batch進行10次迭代訓練實現過擬合。
接著,腳本將執行幾步生成任務,并且最重要的是,保存兩個文件:
- gpt2_124M.bin,其中包含了可用于在C語言環境中加載模型的原始權重;
- gpt2_124M_debug_state.bin,其中包含了額外的調試信息,如輸入數據、目標、logits和損失。?
這些信息對于調試、單元測試以及確保與PyTorch的參考實現完全一致很有幫助。
目前,主要關注的是gpt2_124M.bin文件中的模型權重。有了它們,就可以在C語言環境中初始化模型并開始訓練了。
首先,我們需要編譯代碼:
make train_gpt2
你可以打開Makefile文件,并閱讀里面的注釋。
它會自動檢查你的電腦是否支持OpenMP,這對于以非常低的復雜度來加速代碼運行很有幫助。
當完成train_gpt2的編譯之后,就可以開始運行了:
OMP_NUM_THREADS=8 ./train_gpt2
現在,你需要根據電腦的CPU核心數來設置程序運行的線程數。
然后,程序會加載模型的權重和Token,接著進行幾次迭代的微調過程,這個過程使用了Adam優化算法,學習率設置為0.0001。
最后,程序會根據模型生成一個樣本。
總結來說,代碼實現了模型每一層的數據處理流程,包括前向傳播、反向傳播和參數更新等,并且被組織成了一個完整的循環。
在搭載M3 Max芯片的MacBook Pro上運行時,輸出結果如下:
[GPT-2]
max_seq_len: 1024
vocab_size: 50257
num_layers: 12
num_heads: 12
channels: 768
num_parameters: 124439808
train dataset num_batches: 1192
val dataset num_batches: 128
num_activations: 73323776
val loss 5.252026
step 0: train loss 5.356189 (took 1452.121000 ms)
step 1: train loss 4.301069 (took 1288.673000 ms)
step 2: train loss 4.623322 (took 1369.394000 ms)
step 3: train loss 4.600470 (took 1290.761000 ms)
... (trunctated) ...
step 39: train loss 3.970751 (took 1323.779000 ms)
val loss 4.107781
generated: 50256 16773 18162 21986 11 198 13681 263 23875 198 3152 262 11773 2910 198 1169 6002 6386 2583 286 262 11858 198 20424 428 3135 7596 995 3675 13 198 40 481 407 736 17903 11 329 703 6029 706 4082 198 42826 1028 1128 633 263 11 198 10594 407 198 2704 454 680 1028 262 1027 28860 286 198 3237 323
step 40: train loss 4.377757 (took 1366.368000 ms)
目前,程序生成的結果只是Token ID,我們需要把這些編號轉換成可讀的文本。
這個過程在C語言中實現起來相當簡單,因為涉及到的主要是對應字符串片段的查找和輸出。
現在,我們可以利用一個叫做tiktoken的工具來完成這個任務:
import tiktoken
enc = tiktoken.get_encoding("gpt2")
print(enc.decode(list(map(int, "50256 16773 18162 21986 11 198 13681 263 23875 198 3152 262 11773 2910 198 1169 6002 6386 2583 286 262 11858 198 20424 428 3135 7596 995 3675 13 198 40 481 407 736 17903 11 329 703 6029 706 4082 198 42826 1028 1128 633 263 11 198 10594 407 198 2704 454 680 1028 262 1027 28860 286 198 3237 323".split()))))
打印內容如下:
<|endoftext|>Come Running Away,
Greater conquer
With the Imperial blood
the heaviest host of the gods
into this wondrous world beyond.
I will not back thee, for how sweet after birth
Netflix against repounder,
will not
flourish against the earlocks of
Allay
Karpathy表示,他對Netflix在模型生成結果中的呈現方式非常滿意,因為這顯示出模型仍然保留了其訓練過程中的一些特征。
此外,他也沒有去調整微調的超參數,因此如果能夠優化這些設置,特別是通過延長訓練時間,模型的性能應該會有很大的提升空間。
測試
這里提供一個簡單的單元測試程序,用來驗證我們編寫的C語言代碼是否與PyTorch框架中的代碼實現相匹配。
通過以下命令即可編譯并執行:
make test_gpt2
./test_gpt2
這段代碼首先會加載gpt2_124M_debug_state.bin文件,然后執行一次前向計算。
這個過程會生成模型的預測結果(logits)和損失(loss),并將其與PyTorch的標準實現進行比較。
接下來,它會利用Adam優化算法對模型進行10輪訓練,從而確保訓練的損失與PyTorch的結果一致。
教程
項目最后,Karpathy還附上了一個非常小的教程——
項目地址:https://github.com/karpathy/llm.c/blob/master/doc/layernorm/layernorm.md
它是實現GPT-2模型的單層,即LayerNorm的一個簡單的分步指南。
這是了解如何用C語言實現層的一個很好的起點。
純CUDA也可訓
在訓練開始時,先一次性預分配一大塊一維內存,用于存儲訓練過程中所需的所有數據。
這樣做的好處是,在整個訓練過程中,我們無需再次分配或釋放內存。如此一來,不僅簡化了內存管理,還確保了內存使用量保持不變,優化了數據處理效率。
接下來的核心任務是——手動編寫代碼,實現模型中每一層的數據前向傳播和后向傳播過程,并將這些層按順序連接起來。
此外,為了構建完整的模型,我們還需要實現多個關鍵組件,包括編碼器(encoder)、矩陣乘法(matmul)、自注意力機制(self-attention)、GELU激活函數、殘差連接(residual)、softmax函數和交叉熵損失計算。
Karpathy繼續解釋道,一旦你有了所有的層,你就可以把所有的層串聯起來。
不瞞你說,寫這個過程相當乏味,也很受虐,因為你必須確保所有的指針和張量偏移向量都正確排列。
左圖:在內存中分配一個一維數組,然后將所有模型的權重和激活指向它
右圖:小心地進行所有指針運算
在完成了模型的前向傳播和反向傳播之后,接下來的工作,比如設置數據加載器和調整Adam優化算法,就比較簡單了。
隨后,Karpathy還介紹了自己下一步進行工作是:
一步步地將這個過程遷移到CUDA上,從而大幅提升運算效率,甚至達到接近PyTorch的水平,而且不需要依賴那些復雜的庫。
目前,他已經完成了其中的幾層。
接下來的工作包括減少計算精度——從FP32降到FP16甚至更低,以及添加一些新的層(如RoPE),從而支持更先進的模型架構,例如Llama 2、Mistral、Gemma等。
當然了,等著這一切完成之后,另一期「從頭開始構建」的視頻也會上線。
本文轉自 新智元 ,作者:新智元
