1000行C語言搓出GPT-2!AI大神Karpathy新項(xiàng)目剛上線就狂攬2.5k星
斷更近一個(gè)月,Karpathy終于上線了。
這次不是AI大課,而是帶來一個(gè)新項(xiàng)目。
僅用1000行純C語言訓(xùn)完GPT-2。
想象一下,如果我們能夠不依賴于龐大的PyTorch(245MB)和cPython(107MB)庫,僅僅使用純C語言就能訓(xùn)練大型語言模型(LLM),那會(huì)怎樣?
現(xiàn)在,借助llm.c,這件聽起來似乎不太可能的事,已經(jīng)成為了現(xiàn)實(shí)!
這個(gè)項(xiàng)目的亮點(diǎn)在于,它僅用約1000行簡潔的C代碼,就實(shí)現(xiàn)了在普通計(jì)算機(jī)處理器(CPU)上訓(xùn)練GPT-2模型的能力。
而且,這份代碼不僅可以立即編譯運(yùn)行,其訓(xùn)練結(jié)果也和PyTorch版本的GPT-2完全一致。
之所以選擇GPT-2作為起點(diǎn),是因?yàn)樗鼧?biāo)志著大型語言模型發(fā)展史上的一個(gè)重要里程碑,是第一次以我們現(xiàn)在所熟悉的形式整合了這樣的技術(shù)棧,并且模型權(quán)重也是公開可獲取的。
這一項(xiàng)目剛剛發(fā)布幾個(gè)小時(shí),已經(jīng)獲得了2.5k星。
項(xiàng)目地址:??https://github.com/karpathy/llm.c??
有網(wǎng)友表示,初創(chuàng)公司正在等著Karpathy挖掘新的點(diǎn)子。
很少有人知道,SUNO一開始是nanoGPT的一個(gè)分支。(Suno創(chuàng)業(yè)團(tuán)隊(duì)首款產(chǎn)品Bark受到了nanoGPT的啟發(fā))
或許Karpathy正在嘗試的是重新設(shè)計(jì)LLM架構(gòu),通過llm.c項(xiàng)目去探索一種更簡單、高效的模型訓(xùn)練方法。
「我無法創(chuàng)造的,我就無法理解」。
Karpathy完全讓AI走向大眾化。
那么,僅用C語言如何訓(xùn)出LLM?
千行C代碼訓(xùn)完GPT-2
項(xiàng)目開篇介紹中,Karpathy還提到了自己目前正在進(jìn)行的研究:
- 直接使用CUDA實(shí)現(xiàn),速度會(huì)快得多,可能接近PyTorch。
- 使用SIMD指令加速CPU版本,x86上的AVX2/ARM上的NEON(比如蘋果芯片)。
- 采用更現(xiàn)代的架構(gòu),如Llama2、Gema等。
對(duì)于repo,Karpathy希望同時(shí)維護(hù)干凈、簡單的參考實(shí)現(xiàn)以及更優(yōu)化的版本,這些版本可以接近PyTorch,但只需很少的代碼和依賴項(xiàng)。
快速入門
下載數(shù)據(jù)集,并將其進(jìn)行分詞。Tinyshakepeare數(shù)據(jù)集下載和分詞速度最快:
python prepro_tinyshakespeare.py
打印內(nèi)容如下:
Saved 32768 tokens to data/tiny_shakespeare_val.bin
Saved 305260 tokens to data/tiny_shakespeare_train.bin
其中,.bin文件包含有int32的原始數(shù)據(jù)流,這些整數(shù)代表了通過GPT-2分詞器定義的Token ID。
當(dāng)然,也可以通過運(yùn)行prepro_tinystories.py來對(duì)TinyStories數(shù)據(jù)集進(jìn)行分詞處理。
理論上講,現(xiàn)在已經(jīng)能夠開始訓(xùn)練模型了。但是,目前基于CPU和FP32的參考代碼運(yùn)行效率極低,無法從零開始訓(xùn)練這些模型。
因此,我們選擇先用OpenAI發(fā)布的GPT-2模型權(quán)重進(jìn)行初始化,再對(duì)模型進(jìn)行微調(diào)。
為了這個(gè)目的,我們需要下載GPT-2模型的權(quán)重文件,并把它們作為檢查點(diǎn)保存下來,這樣就可以在C語言環(huán)境中進(jìn)行加載了:
python train_gpt2.py
這個(gè)腳本的作用是下載GPT-2(124M)模型,并對(duì)單個(gè)數(shù)據(jù)batch進(jìn)行10次迭代訓(xùn)練實(shí)現(xiàn)過擬合。
接著,腳本將執(zhí)行幾步生成任務(wù),并且最重要的是,保存兩個(gè)文件:
- gpt2_124M.bin,其中包含了可用于在C語言環(huán)境中加載模型的原始權(quán)重;
- gpt2_124M_debug_state.bin,其中包含了額外的調(diào)試信息,如輸入數(shù)據(jù)、目標(biāo)、logits和損失。?
這些信息對(duì)于調(diào)試、單元測(cè)試以及確保與PyTorch的參考實(shí)現(xiàn)完全一致很有幫助。
目前,主要關(guān)注的是gpt2_124M.bin文件中的模型權(quán)重。有了它們,就可以在C語言環(huán)境中初始化模型并開始訓(xùn)練了。
首先,我們需要編譯代碼:
make train_gpt2
你可以打開Makefile文件,并閱讀里面的注釋。
它會(huì)自動(dòng)檢查你的電腦是否支持OpenMP,這對(duì)于以非常低的復(fù)雜度來加速代碼運(yùn)行很有幫助。
當(dāng)完成train_gpt2的編譯之后,就可以開始運(yùn)行了:
OMP_NUM_THREADS=8 ./train_gpt2
現(xiàn)在,你需要根據(jù)電腦的CPU核心數(shù)來設(shè)置程序運(yùn)行的線程數(shù)。
然后,程序會(huì)加載模型的權(quán)重和Token,接著進(jìn)行幾次迭代的微調(diào)過程,這個(gè)過程使用了Adam優(yōu)化算法,學(xué)習(xí)率設(shè)置為0.0001。
最后,程序會(huì)根據(jù)模型生成一個(gè)樣本。
總結(jié)來說,代碼實(shí)現(xiàn)了模型每一層的數(shù)據(jù)處理流程,包括前向傳播、反向傳播和參數(shù)更新等,并且被組織成了一個(gè)完整的循環(huán)。
在搭載M3 Max芯片的MacBook Pro上運(yùn)行時(shí),輸出結(jié)果如下:
[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)
目前,程序生成的結(jié)果只是Token ID,我們需要把這些編號(hào)轉(zhuǎn)換成可讀的文本。
這個(gè)過程在C語言中實(shí)現(xiàn)起來相當(dāng)簡單,因?yàn)樯婕暗降闹饕菍?duì)應(yīng)字符串片段的查找和輸出。
現(xiàn)在,我們可以利用一個(gè)叫做tiktoken的工具來完成這個(gè)任務(wù):
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()))))
打印內(nèi)容如下:
<|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表示,他對(duì)Netflix在模型生成結(jié)果中的呈現(xiàn)方式非常滿意,因?yàn)檫@顯示出模型仍然保留了其訓(xùn)練過程中的一些特征。
此外,他也沒有去調(diào)整微調(diào)的超參數(shù),因此如果能夠優(yōu)化這些設(shè)置,特別是通過延長訓(xùn)練時(shí)間,模型的性能應(yīng)該會(huì)有很大的提升空間。
測(cè)試
這里提供一個(gè)簡單的單元測(cè)試程序,用來驗(yàn)證我們編寫的C語言代碼是否與PyTorch框架中的代碼實(shí)現(xiàn)相匹配。
通過以下命令即可編譯并執(zhí)行:
make test_gpt2
./test_gpt2
這段代碼首先會(huì)加載gpt2_124M_debug_state.bin文件,然后執(zhí)行一次前向計(jì)算。
這個(gè)過程會(huì)生成模型的預(yù)測(cè)結(jié)果(logits)和損失(loss),并將其與PyTorch的標(biāo)準(zhǔn)實(shí)現(xiàn)進(jìn)行比較。
接下來,它會(huì)利用Adam優(yōu)化算法對(duì)模型進(jìn)行10輪訓(xùn)練,從而確保訓(xùn)練的損失與PyTorch的結(jié)果一致。
教程
項(xiàng)目最后,Karpathy還附上了一個(gè)非常小的教程——
項(xiàng)目地址:https://github.com/karpathy/llm.c/blob/master/doc/layernorm/layernorm.md
它是實(shí)現(xiàn)GPT-2模型的單層,即LayerNorm的一個(gè)簡單的分步指南。
這是了解如何用C語言實(shí)現(xiàn)層的一個(gè)很好的起點(diǎn)。
純CUDA也可訓(xùn)
在訓(xùn)練開始時(shí),先一次性預(yù)分配一大塊一維內(nèi)存,用于存儲(chǔ)訓(xùn)練過程中所需的所有數(shù)據(jù)。
這樣做的好處是,在整個(gè)訓(xùn)練過程中,我們無需再次分配或釋放內(nèi)存。如此一來,不僅簡化了內(nèi)存管理,還確保了內(nèi)存使用量保持不變,優(yōu)化了數(shù)據(jù)處理效率。
接下來的核心任務(wù)是——手動(dòng)編寫代碼,實(shí)現(xiàn)模型中每一層的數(shù)據(jù)前向傳播和后向傳播過程,并將這些層按順序連接起來。
此外,為了構(gòu)建完整的模型,我們還需要實(shí)現(xiàn)多個(gè)關(guān)鍵組件,包括編碼器(encoder)、矩陣乘法(matmul)、自注意力機(jī)制(self-attention)、GELU激活函數(shù)、殘差連接(residual)、softmax函數(shù)和交叉熵?fù)p失計(jì)算。
Karpathy繼續(xù)解釋道,一旦你有了所有的層,你就可以把所有的層串聯(lián)起來。
不瞞你說,寫這個(gè)過程相當(dāng)乏味,也很受虐,因?yàn)槟惚仨毚_保所有的指針和張量偏移向量都正確排列。
左圖:在內(nèi)存中分配一個(gè)一維數(shù)組,然后將所有模型的權(quán)重和激活指向它
右圖:小心地進(jìn)行所有指針運(yùn)算
在完成了模型的前向傳播和反向傳播之后,接下來的工作,比如設(shè)置數(shù)據(jù)加載器和調(diào)整Adam優(yōu)化算法,就比較簡單了。
隨后,Karpathy還介紹了自己下一步進(jìn)行工作是:
一步步地將這個(gè)過程遷移到CUDA上,從而大幅提升運(yùn)算效率,甚至達(dá)到接近PyTorch的水平,而且不需要依賴那些復(fù)雜的庫。
目前,他已經(jīng)完成了其中的幾層。
接下來的工作包括減少計(jì)算精度——從FP32降到FP16甚至更低,以及添加一些新的層(如RoPE),從而支持更先進(jìn)的模型架構(gòu),例如Llama 2、Mistral、Gemma等。
當(dāng)然了,等著這一切完成之后,另一期「從頭開始構(gòu)建」的視頻也會(huì)上線。
本文轉(zhuǎn)自 新智元 ,作者:新智元
