Llama dari Nol, Cara Mengimplementasikan Paper Tanpa Menangis
(blog.briankitano.com)- Brian Kitano membuat sendiri versi mini Llama dengan TinyShakespeare, dan menyimpulkan bahwa implementasi paper paling aman dilakukan dengan mulai dari model kecil, lalu mengganti komponen satu per satu sambil melatih dan mengevaluasi setiap kali ada perubahan
- Ia lebih dulu menyiapkan fungsi bantu validasi seperti pembagian data, pembuatan batch, evaluasi loss, dan fungsi generasi, lalu memastikan model sederhana bisa dikompilasi dan dilatih sebelum menambahkan komponen Llama
- RMSNorm, RoPE, dan SwiGLU ditambahkan secara berurutan, sambil memeriksa apakah tiap layer bekerja sesuai harapan lewat shape tensor, sifat rumus, dan attention map
- Saat causal mask dihapus dari attention berbasis RoPE, validation loss turun hingga 0.16, tetapi kualitas generasi memburuk karena terjadi kebocoran informasi akibat model melihat token masa depan
- Versi mini Llama final terdiri dari 4 blok dengan sekitar 2,37 juta parameter, menurunkan validation loss hingga sekitar 1.0, dan menunjukkan bahwa aliran gradient serta learning rate schedule juga perlu diperiksa
Mulai dari kecil dan bangun keyakinan secara iteratif
- Inti implementasi paper adalah memulai dari model kecil, mengganti komponen satu per satu, lalu mengulang pelatihan dan evaluasi setiap kali ada perubahan
- Pertama, ia menyiapkan fungsi-fungsi bantu untuk memeriksa model secara kuantitatif
- pembagian data
- loop pelatihan
- visualisasi loss
- evaluasi validation loss
- Alih-alih langsung memindahkan semua komponen paper sekaligus, ia juga menyiapkan fungsi evaluasi kualitatif untuk melihat hasil generasi dari model sederhana dan cepat yang sudah pernah ia implementasikan
- Layer tensor diperiksa dengan
.shape,assert, danplt.imshow; alih-alih langsung mengoptimalkan perkalian matriks sejak awal, ia lebih dulu mengecek hasil yang diharapkan secara manual lalu mengefisienkannya dengan fungsitorch - Pengujian perlu dilakukan dengan mengubah ukuran batch, panjang sekuens, dan dimensi embedding; kode yang hanya benar untuk satu ukuran bisa rusak saat inferensi
Dataset dan pengaturan dasar
- Target implementasi adalah versi sangat diperkecil dari Llama milik Meta AI, dan data latih yang dipakai adalah TinyShakespeare
- Llama dilatih dengan 1.4T token, tetapi di sini digunakan TinyShakespeare dengan skala sekitar 1,11 juta karakter
- Llama asli menggunakan tokenizer byte-pair encoding SentencePiece, tetapi implementasi ini memakai tokenizer tingkat karakter yang sederhana
- ukuran vocabulary adalah 65
- karena dataset kecil, cara penyimpanan di memori tidak dioptimalkan secara khusus
- Dictionary
MASTER_CONFIGdipakai untuk mengelola pengaturan model sepertivocab_size,batch_size,context_window, dand_model- tujuannya untuk mengurangi konstanta dan magic number serta membuat kode lebih mudah dibaca
- Fungsi
get_batchesmembagi data menjadi train 80%, val 10%, test 10%, lalu membuat inputxdan labelyyang bergeser satu karakter dari titik awal acak
Memastikan kompilasi dan pelatihan dengan model dasar
- Model pertama adalah
SimpleBrokenModel, yang terdiri dari embedding dan jaringan feed-forward sederhanann.EmbeddingLinearReLULinear
- Dalam implementasi paper, mengatakan model “berfungsi” berarti harus memenuhi dua syarat sekaligus
- kompilasi: shape tensor cocok antar-layer
- pelatihan: loss benar-benar turun
- Fungsi
evaluate_lossmengambil sampel 10 batch dari split train dan val untuk menghitung rata-rata loss - Setelah dilatih selama 1000 epoch,
SimpleBrokenModelmemiliki validation loss sekitar 3.94, hampir tidak turun dari cross-entropy awal 4.17 - Penyebabnya adalah nilai yang sudah melalui softmax dimasukkan ke
F.cross_entropyF.cross_entropymilik PyTorch menerima logits yang belum dinormalisasi secara langsungSimpleModelyang menghapus softmax berhasil menurunkan validation loss hingga sekitar 2.51
- Setelah itu ditambahkan fungsi
generateuntuk memeriksa langsung karakter yang dibuat model, dan model dasar pun setidaknya berada dalam kondisi loss yang menurun meski belum sempurna
Komponen Llama 1: RMSNorm
- Dibanding Transformer asli, Llama memakai tiga perubahan arsitektur utama
- pre-normalization RMSNorm
- Rotary embeddings
- fungsi aktivasi SwiGLU
- Transformer asli menggunakan BatchNormalization, sedangkan Llama memakai RMSNorm, yang menskalakan dengan variance tanpa melakukan centering pada vektor
- Jika Transformer asli menerapkan normalisasi pada keluaran layer attention sebagai post-normalization, Llama justru menerapkannya lebih dulu pada input sebagai pre-normalization
RMSNormyang diimplementasikan mengasumsikan shape input(batch, seq_len, d_model)- Hasil RMSNorm diuji lewat sifat bahwa norma layer menjadi akar kuadrat dari jumlah elemen layer
assert- row-wise comparison
torch.allclose
SimpleModel_RMS, yaitu model dasar yang ditambah RMSNorm, sedikit menurunkan validation loss ke sekitar 2.5015
Komponen Llama 2: RoPE dan causal mask
- RoPE adalah metode positional encoding untuk Transformer yang merepresentasikan posisi token sebagai rotasi embedding
get_rotary_matrixmembuat matriks rotasi per posisi untuk context window dan dimensi embedding- Implementasi RoPE diuji dengan sifat berikut
- inner product dari dua vektor yang diputar pada posisi
mdannharus cocok dengan rotasi posisi relatifn-m
- inner product dari dua vektor yang diputar pada posisi
RoPEAttentionHeadmembuatw_q,w_k, danw_v, menerapkan rotasi RoPE pada query dan key, lalu memakaiF.scaled_dot_product_attention- Perbedaan shape tensor antara waktu pelatihan dan waktu inferensi perlu diperhatikan
- saat pelatihan, sering kali bentuknya sesuai konfigurasi seperti
(config['batch_size'], config['context_window'], config['d_model']) - saat inferensi, model bisa memproses satu contoh seperti
(1, 1, config['d_model']) - di dalam
forward, pengindeksan harus berdasarkan shape dari input, bukan dari nilai konfigurasi model
- saat pelatihan, sering kali bentuknya sesuai konfigurasi seperti
- Model yang menambahkan multi-head attention RoPE tanpa causal mask menurunkan validation loss secara drastis hingga 0.1623, tetapi hasil generasinya buruk seperti
OOOO...danIIII... - Setelah attention map diperiksa, terlihat semua posisi merujuk ke semua posisi lain, sehingga pada prediksi token berikutnya terjadi kebocoran informasi karena model melihat token masa depan
- Setelah diganti dengan
RoPEMaskedAttentionHeadyang menerapkanis_causal=TruepadaF.scaled_dot_product_attention, perhatian ke area segitiga atas yang mewakili masa depan menjadi nyaris 0 - Setelah causal mask diterapkan, validation loss menjadi 2.0815, lalu turun lagi menjadi 1.8985 dengan pelatihan lebih lama
Komponen Llama 3: SwiGLU dan menumpuk blok
- Llama mengganti nonlinieritas ReLU dengan fungsi aktivasi SwiGLU
SwiGLUyang diimplementasikan adalah swish-gated linear unit yang menggunakan dua transformasi linear dan parameterbetayang dapat dipelajariRopeModelyang memasukkan SwiGLU ke bagian feed-forward memiliki 592.706 parameter dan validation loss sekitar 1.8963- Setelah itu dibuat
LlamaBlockuntuk menggabungkan susunan berikut ke dalam satu blok- pre-normalization RMSNorm
- masked RoPE multi-head attention
- residual connection
- pre-normalization RMSNorm
- feed-forward SwiGLU
- residual connection
- Model
Llamafinal menetapkann_layers=4dan menumpuk 4LlamaBlockdengannn.SequentialberbasisOrderedDict - Jumlah parameter model final adalah 2.370.246, dan hasil pelatihannya sebagai berikut
- setelah pelatihan awal 4 layer, validation loss 1.5532
- setelah dilatih lebih lanjut hingga 10.000 epoch, validation loss 1.1479
- setelah pelatihan tambahan, validation loss 0.9997
- loss untuk satu batch pada split test adalah 1.2358
Hasil generasi dan pemeriksaan debugging
- Model final dapat menghasilkan nama, pemenggalan baris, dan potongan kata yang mirip gaya Shakespeare, tetapi kualitas kalimat sebenarnya masih terbatas
- Loss cross-entropy bisa diintuisikan dari sudut pandang pemilihan token
- loss awal 4.17 hampir setara dengan pemilihan acak pada vocabulary berukuran 65
- loss 1.08 dapat ditafsirkan seperti memilih secara acak dari sekitar 2,9 token
- Aliran gradient diperiksa dengan fungsi
show_grads- fungsi ini menghitung rasio gradient bernilai absolut kecil pada setiap parameter
- jika gradient sebagian besar parameter tidak mendekati 0, maka alirannya tergolong baik
- Llama asli menggunakan learning schedule Cosine Annealing, tetapi pada implementasi ini hasil eksperimennya justru lebih buruk
- Dalam eksperimen Cosine Annealing, bahkan pada tolerance yang sangat rendah, attention bias hampir tidak menerima sinyal, dan karena penyebabnya belum jelas, lebih aman memulai dari pendekatan yang sederhana dalam implementasi nyata
1 komentar
Pendapat di Hacker News
Tampaknya ada bug dalam implementasi SwiGLU: dalam makalah rujukan, beta pada feed-forward network adalah konstanta, bukan nilai yang dapat dilatih, dan ditulis sebagai
FFnSwiGLU = Swish1...Ini mengacu pada persamaan 6 di https://arxiv.org/pdf/2002.05202.pdf
Dalam implementasi resmi llama pun beta konstanta dihilangkan: https://github.com/facebookresearch/llama/blob/main/llama/mo...
Jika melihat baris-baris
"feedforward.1.beta', 0.0"pada log blog, beta merosot menjadi 0 selama pelatihan, padahal seharusnya konstanta 1Jaringan juga sering beradaptasi terhadap perubahan, disengaja atau tidak, dan setelah pelatihan berbagai variasi arsitektur bisa berperilaku mirip, sehingga kadang ambigu apakah harus benar-benar sama dengan aslinya
Salah satu cara menemukan kesalahan seperti ini adalah mencocokkan output secara persis dengan implementasi referensi. Seperti model tiny-random dari HuggingFace, meskipun bobotnya acak, output harus sama persis; jika berbeda, itu sinyal adanya bug
Namun cara ini terutama efektif untuk bug yang muncul saat inferensi, sedangkan masalah yang hanya terjadi saat pemrosesan data, optimizer, atau pelatihan lebih sulit ditangkap
Secara pribadi saya menduga ini karena sifatnya yang autoregresif dan mirip ODE, tetapi saya tidak cukup yakin
Pekerjaannya bagus, tetapi
SimpleBrokenModeldanSimpleModelawal memiliki cukup banyak komputasi yang terbuang. Urutannya adalahembedding 65 -> 128,linear 128 -> 128,ReLU,linear 128 -> 65; karena tidak ada nonlinieritas di antara dua layer pertama dan keduanya linier, layer linier kedua pada dasarnya tidak bergunaModel ini pada akhirnya sama dengan MLP klasik dengan satu hidden layer, dan dari sisi FLOPS, operasi
128*128=16kterbuang dari total128*128+65*128=24kLayer embedding adalah struktur khusus yang mengubah indeks token menjadi vektor embedding, jadi sepertinya tidak bisa dihapus
Secara keseluruhan ini menunjukkan prinsip-prinsip dasar dengan baik. Saya terutama suka kalimat “pakailah
.shapesecara religius.assertdanplt.imshowadalah temanmu”, dan pra-kondisi serta pasca-kondisi shape harus selalu di-assertSaya juga penasaran apakah
bearatautypeguardmendukung pemeriksaan semacam ini sebagai decoratorNamun bagian “pilih model yang kecil, sederhana, dan cepat, lalu buat helper untuk mengevaluasi secara kualitatif” sepertinya maksudnya evaluasi kuantitatif. Dengan begitu ada baseline numerik untuk dibandingkan dengan teknik yang lebih canggih
Saran untuk mengimplementasikan komponen makalah satu per satu juga perlu lebih presisi. Makalah biasanya mencoba beberapa perubahan sekaligus lalu menunjukkan kontribusi tiap elemen melalui ablation experiment, jadi menurut saya lebih baik mulai dari perubahan arsitektur inti, lalu mengevaluasi setiap perubahan atomik sesuai urutan dampak terbesar dalam ablation experiment sambil menjaga dependensi
bearatautypeguard, berkat https://peps.python.org/pep-0646/, sebagian hal bisa langsung dimasukkan lewat anotasi tipe PythonMisalnya, shape per sumbu bisa diekspresikan dalam tipe seperti
ndarray[float, Dim1, *Shape], dan shape keluaran bisa dioverload sesuai nilaiaxisbear/typeguardMeski begitu, Python tampaknya sulit sebagus Julia. Sistem tipe Julia bisa jauh lebih mudah menjamin apakah ukuran matriks cocok
Saya penasaran apa prinsip penggunaan SwiGLU alih-alih ReLU. Entah para penulis sekadar mencoba semua fungsi nonlinier yang memungkinkan, atau ada alasan yang lebih mendalam
Karena bearblog sedang terkena DDoS, saya tinggalkan repositorinya: https://github.com/bkitano/llama-from-scratch
Dari sudut pandang orang yang sedang belajar AI, saya mencoba merangkum secara singkat istilah-istilah yang muncul di tulisan itu. Token adalah pengenal bilangan bulat yang merepresentasikan potongan teks, dan dalam LLM, potongan karakter yang sering digunakan dikelompokkan dalam ukuran kosakata yang terbatas
Fungsi loss adalah nilai yang mengukur selisih antara prediksi dan jawaban benar; makin rendah makin baik. PyTorch adalah library untuk menangani tensor dan jaringan neural, sedangkan tensor adalah array angka multidimensi yang mencakup skalar, vektor, dan matriks
Jaringan neural adalah struktur koneksi neuron yang memiliki bobot dan bias, dan layer linear adalah struktur sederhana di mana semua input dan output saling terhubung. ReLU adalah fungsi aktivasi seperti
Math.max(0, x); jika hanya menumpuk layer linear, pada akhirnya hasilnya sama saja dengan satu fungsi linear, jadi non-linearitas ditambahkan untuk meningkatkan kemampuan belajarGradien adalah besaran perubahan numerik yang dihitung selama pelatihan untuk membuat model lebih akurat, dan batch normalization adalah metode yang membantu pelatihan dengan menyesuaikan angka-angka yang mengalir. Positional encoding memberi tahu posisi relatif token-token dalam bentuk vektor
Operator
@di Python adalah alias untuk__matmul__dan digunakan untuk perkalian matriks. Epoch berarti melatih seluruh dataset satu kali, sedangkan batch adalah jumlah data yang dimasukkan sekaligus sebelum pembaruan parameterAttention adalah inti yang membuat LLM bekerja; ia memproses token input secara paralel untuk membuat tensor perantara, lalu menggunakannya untuk menghasilkan token output
Misalnya,
writyang sama-sama muncul dalamwriting,written, danwriterbisa menjadi satu token, danwriterbisa ditokenisasi menjadiwritdanerEmbedding adalah tahap yang mengubah token-token seperti ini menjadi representasi angka yang unik
Jika ada implementasi dan checkpoint yang sudah ada untuk model tersebut, cara paling efektif untuk memastikan implementasi sendiri benar adalah memuat checkpoint itu lalu membandingkan outputnya
Jika outputnya tidak cocok, biasanya ada detail implementasi yang salah, dan kita bisa menelusuri setiap layer secara sistematis untuk menemukan perbedaan sebenarnya. Dalam proses itu, bisa saja kita menemukan keanehan pada implementasi yang sudah ada
Ini adalah soal model itu sendiri, sedangkan pelatihan adalah sumbu yang terpisah. Meski begitu, jika hyperparameter dibuat cukup mirip, saat implementasi modelnya benar biasanya hasilnya akan cukup baik
Baik cara membaca paper maupun isi paper tersebut sama-sama bagus, dan saya juga merekomendasikan seri Makemore dari Karpathy
Saran-saran ringkasnya sangat bagus, dan menurut saya saran untuk melakukan assert pada shape tensor berlaku juga untuk library aljabar linear umum mana pun. Saat menulis kode aljabar linear yang kompleks, sangat penting untuk bergerak dalam langkah kecil dan menulis kode secara defensif
Memprogram aljabar linear di bahasa-bahasa arus utama itu mengerikan karena tidak ada pemeriksaan shape saat compile time. Shape tensor seharusnya menjadi bagian dari tipe, dan jika mencoba mengalikan
3x4dengan3x4tanpa transpose, kompilasinya sendiri seharusnya gagalGagal pada operasi dengan dimensi yang tidak cocok setelah menjalankan komputasi panjang itu benar-benar skenario terburuk
Menurut saya perangkat pada tensor PyTorch juga seharusnya bertipe statis. Saat ini, jika mencoba mengalikan tensor di memori CPU dengan tensor di memori GPU, yang muncul adalah error runtime