--- language: - id license: mit tags: - text-generation - from-scratch - gpt - transformer - indonesian datasets: - custom --- # Indigo Model bahasa kecil GPT-style yang dibangun **dari nol** (tanpa library transformers) sebagai proyek pembelajaran. Backend **PyTorch**; tersedia juga varian [TensorFlow/Keras terpisah](https://huggingface.co/adyoi/indigo-tf). Bobot disimpan dalam format aman `.safetensors`. ## Arsitektur | Komponen | Nilai default | |---|---| | Tipe | Decoder-only transformer (pre-LN, SDPA) | | Parameter | ~0.81M (default) | | Layer / Head | 4 / 4 | | Dimensi embedding | 128 | | Konteks (block_size) | 128 token | | Tokenizer | `char` (karakter) atau `bpe` (byte-pair encoding) | | Weight tying | Ya (output head = token embedding) | | Aktivasi | GELU (MLP) | | Attention | Multi-head causal self-attention + KV-cache | ## Struktur file ``` indigo/ model.py Arsitektur GPT (CausalSelfAttention, MLP, Block, GPT, sampling) common.py Utilitas bersama (clean_text, meta save/load, morphological analysis) bpe.py Tokenizer BPE level-byte minimal tokenizer.py Tokenizer karakter (char-level) train.py Training loop (best-checkpoint, resume, split val, cosine LR schedule) generate.py Generasi teks (top-k, top-p, repetition penalty, guard kamus) pipeline.py Pipeline end-to-end (data collection → training → manifest → finalize/push) eval.py Evaluasi batched antar-checkpoint (nats/token, nats/karakter) audit_kamus.py Audit wordlist kamus terhadap KBBI daring out/ indigo_best.safetensors Bobot terbaik (dipilih berdasarkan validasi) indigo_best_meta.json Metadata terbaik indigo.safetensors Bobot checkpoint akhir indigo_meta.json Metadata akhir indigo_optimizer.pt State optimizer (untuk resume) run_info.json Info run terakhir yang di-finalize data/ sample.txt Teks contoh Indonesia (training default) kamus_id.txt Wordlist Indonesia (~70K kata, gitignored) prefiks.txt Daftar prefiks Indonesia untuk morphological guard sufixs.txt Daftar sufiks Indonesia untuk morphological guard ``` ## Persyaratan ``` torch>=2.0 safetensors>=0.4 ``` Install: ```bash pip install -r requirements.txt ``` --- ## `train.py` — Training Model Script utama untuk melatih model GPT dari nol. ### Argumen | Argumen | Tipe | Default | Deskripsi | |---|---|---|---| | `--data` | str (nargs+) | `data/sample.txt` | Path file/folder teks untuk training. Bisa banyak, spasi-separated. Folder akan dicari rekursif untuk file `.txt`. | | `--out` | str | `out` | Folder output checkpoint (`.safetensors` + `_meta.json` + `_optimizer.pt`). | | `--steps` | int | `2000` | Jumlah total langkah training. | | `--batch-size` | int | `32` | Jumlah sampel per batch. Batch lebih besar → gradient lebih stabil tapi butuh lebih banyak VRAM. | | `--block-size` | int | `128` | Panjang konteks token per sampel (sequence length). Semakin besar → model bisa lihat lebih jauh tapi butuh lebih banyak memori. | | `--lr` | float | `3e-4` | Learning rate maksimum. Cosine decay dari lr ini ke 10% lr selama training. | | `--warmup` | int | `100` | Jumlah langkah warmup linear sebelum cosine decay dimulai. | | `--weight-decay` | float | `0.1` | L2 regularization / weight decay untuk AdamW optimizer. | | `--dropout` | float | `0.1` | Dropout rate (0.0 = nonaktif). Membantu mencegah overfitting pada data kecil. | | `--n-layer` | int | `4` | Jumlah blok transformer bertumpuk. Lebih banyak layer → model lebih dalam tapi lebih lambat. | | `--n-head` | int | `4` | Jumlah head per attention layer. Harus habis membagi `--n-embd`. | | `--n-embd` | int | `128` | Dimensi embedding / hidden size. Parameter ≈ 12 × n_layer × n_embd². | | `--tokenizer` | `char`/`bpe` | `char` | Jenis tokenizer. `char` = cepat, `bpe` = lebih efisien untuk teks panjang. | | `--vocab-size` | int | `512` | Ukuran vocab untuk BPE (diabaikan jika `--tokenizer char`). | | `--eval-interval` | int | `200` | Evaluasi validasi setiap N langkah. Set `0` untuk skip validasi. | | `--eval-iters` | int | `20` | Jumlah batch untuk estimasi loss validasi. | | `--seed` | int | `1337` | Seed random untuk reproduktibilitas. | | `--val-fraction` | float | `0.1` | Proporsi file untuk validasi (split per-file, bukan per-karakter). | | `--init-from` | str | `None` | Path checkpoint untuk melanjutkan training (resume). Muat model + optimizer + step. | | `--device` | `auto`/`cpu`/`cuda` | `auto` | Device training. `auto` = CUDA jika tersedia, else CPU. | ### Contoh ```bash # Training dasar python train.py --data data/sample.txt --steps 2000 # Training dengan BPE tokenizer python train.py --data data/teks.txt --tokenizer bpe --vocab-size 512 # Model lebih besar, training lebih lama python train.py --data data/ --n-layer 6 --n-head 8 --n-embd 256 --steps 5000 # Resume dari checkpoint python train.py --init-from out/indigo_best.safetensors --steps 1000 # Tanpa validasi (data hanya 1 file) python train.py --data data/combined.txt --val-fraction 0 --eval-interval 0 ``` ### Output - `out/indigo_best.safetensors` + `out/indigo_best_meta.json` — checkpoint terbaik (val loss minimum) - `out/indigo.safetensors` + `out/indigo_meta.json` — checkpoint akhir - `out/indigo_optimizer.pt` — state optimizer (untuk resume) ### Learning Rate Schedule ``` Step 0 → warmup: linear dari 0 → lr_max Step warmup → total: cosine decay dari lr_max → 0.1 × lr_max ``` Formula cosine: `0.1 × lr + 0.45 × lr × (1 + cos(π × progress))` --- ## `generate.py` — Generasi Teks Generate teks dari checkpoint Indigo dengan berbagai opsi sampling. ### Argumen | Argumen | Tipe | Default | Deskripsi | |---|---|---|---| | `--ckpt` | str | `out/indigo_best.safetensors` | Path ke file checkpoint model. | | `--prompt` | str | `""` | Teks awal (prompt) untuk memulai generasi. | | `--max-new` | int | `300` | Jumlah token baru yang akan dihasilkan (bukan panjang total). | | `--temperature` | float | `0.8` | Skala randomness: `0.0` ≈ greedy, `0.8` ≈ standar, `>1.0` ≈ random. | | `--top-k` | int | `40` | Batasi sampling ke k token teratas. `0` = nonaktif. | | `--top-p` | float | `1.0` | Nucleus sampling: batasi kumulatif probabilitas. `1.0` = nonaktif. | | `--repetition-penalty` | float | `1.0` | Penalti pengulangan token. `1.0` = nonaktif, `>1.0` = kurangi pengulangan. | | `--seed` | int | `None` | Seed random. `None` = tidak ditentukan (random setiap kali). | | `--device` | `auto`/`cpu`/`cuda` | `auto` | Device untuk inferensi. | | `--guard` | str | `None` | Path file kamus (satu kata per baris). Generate beberapa kandidat → pilih yang rasio kata dikenal tertinggi. | | `--guard-prefiks` | str | `None` | Path file prefiks Indonesia. Default: `data/prefiks.txt` bila ada. | | `--guard-sufiks` | str | `None` | Path file sufiks Indonesia. Default: `data/sufiks.txt` bila ada. | | `--guard-tries` | int | `5` | Jumlah kandidat generate saat `--guard` aktif. | | `--guard-min` | float | `0.6` | Rasio kata dikenal minimum. Berhenti generate lebih awal jika tercapai. | ### Contoh ```bash # Generasi dasar python generate.py --prompt "Indigo" --max-new 300 # Sampling lebih random python generate.py --prompt "hello" --temperature 1.0 --top-k 60 --top-p 0.9 # Dengan guard kamus (pilih output paling koheren) python generate.py --prompt "kepekaan" --max-new 120 --guard data/kamus_id.txt # Repetition penalty untuk mengurangi pengulangan python generate.py --prompt "cerita" --repetition-penalty 1.2 --max-new 200 ``` ### Guard Kamus Saat `--guard` aktif, generate.py akan: 1. Generate `--guard-tries` kandidat teks (seed berbeda tiap kandidat) 2. Untuk setiap kandidat, hitung rasio kata yang dikenal kamus 3. Pilih kandidat dengan rasio tertinggi 4. Berhenti lebih awal jika rasio >= `--guard-min` Kata berimbuhan dicek lewat formula morfologi: - Hapus sufiks → cek akar - Hapus prefiks → cek akar (+ asimilasi: `meny-`→`s`, `pem-`→`p`) --- ## `pipeline.py` — Pipeline End-to-End Rangkai semua tahap: kumpul data → training → evaluasi → manifest → finalize → push. ### Argumen | Argumen | Tipe | Default | Deskripsi | |---|---|---|---| | `--tag` | str | **wajib** | Nama run. Semua artefak disimpan di `runs//`. | | `--data` | str (nargs*) | `[]` | File/folder teks lokal tambahan (banyak, spasi-separated). | | `--hf-dataset` | str | `None` | Repo dataset HF untuk menarik file teks (mis. `adyoi/indigo`). | | `--hf-patterns` | str (nargs*) | `["*.txt", "*.md"]` | Pola file yang diambil dari HF. | | `--format-qa` | flag | `False` | Auto-convert Alpaca JSON (`instruction`/`output`) ke `.txt` sebelum training. | | `--runs` | str | `runs` | Folder root untuk semua run. | | `--device` | str | `None` | Device training (diteruskan ke `train.py`, default: auto). | | `--finalize` | flag | `False` | Promosikan checkpoint terbaik ke folder `out/` kanonik. | | `--push` | flag | `False` | Upload checkpoint terbaik ke repo HF. | | `--repo` | str | `adyoi/indigo` | Repo HF tujuan upload. | | `--guard` | str | `None` | File kamus untuk metrik rasio ejaan di manifest. | | `--guard-max-new` | int | `120` | Jumlah token generate untuk evaluasi guard. | | `--guard-prefiks` | str | `None` | File prefiks Indonesia. | | `--guard-sufiks` | str | `None` | File sufiks Indonesia. | **Hyperparameter training** (diteruskan ke `train.py`): | Argumen | Tipe | Default | Deskripsi | |---|---|---|---| | `--steps` | int | `2000` | Jumlah langkah training. | | `--batch-size` | int | `32` | Batch size. | | `--block-size` | int | `128` | Panjang konteks. | | `--n-layer` | int | `4` | Jumlah layer transformer. | | `--n-head` | int | `4` | Jumlah head attention. | | `--n-embd` | int | `128` | Dimensi embedding. | | `--dropout` | float | `0.1` | Dropout rate. | | `--lr` | float | `3e-4` | Learning rate. | | `--warmup` | int | `100` | Langkah warmup. | | `--weight-decay` | float | `0.1` | Weight decay. | | `--eval-interval` | int | `200` | Evaluasi setiap N langkah. | | `--eval-iters` | int | `20` | Jumlah batch evaluasi. | | `--seed` | int | `1337` | Seed random. | | `--init-from` | str | `None` | Checkpoint untuk resume. | | `--tokenizer` | `char`/`bpe` | `char` | Jenis tokenizer. | | `--vocab-size` | int | `512` | Vocab size untuk BPE. | | `--val-fraction` | float | `0.1` | Proporsi file validasi. | ### Contoh ```bash # Data lokal saja python pipeline.py --tag run01 --data data/sample.txt --steps 2000 # Data dari HF + BPE tokenizer python pipeline.py --tag run02 --data data/teks.txt --hf-dataset adyoi/indigo \ --tokenizer bpe --vocab-size 512 --steps 900 --device cpu # Dataset Alpaca JSON dari HF python pipeline.py --tag qa01 \ --hf-dataset rohanrdy/CS-Theory-QA-Dataset \ --format-qa --steps 3000 --n-layer 6 --n-embd 256 # Full pipeline: train + finalize + push python pipeline.py --tag final01 --data data/ --steps 2000 --finalize --push # Dengan guard kamus untuk metrik kualitas python pipeline.py --tag guarded --data data/ --steps 2000 --guard data/kamus_id.txt ``` ### Output - `runs//data/` — data yang digunakan (copy dari sumber) - `runs//ckpt/` — checkpoint training - `runs//manifest.json` — statistik + metadata run (termasuk `kamus_ratio` jika `--guard` aktif) --- ## `eval.py` — Evaluasi Batched Bandingkan beberapa checkpoint pada set uji tetap. Metrik: - **nats/token**: loss rata-rata per token (cross-entropy). Lebih rendah = lebih baik. - **nats/karakter**: loss per karakter (dikoreksi dengan compression ratio). Memungkinkan perbandingan antar tokenizer. ### Argumen | Argumen | Tipe | Default | Deskripsi | |---|---|---|---| | `--ckpt` | str (nargs+) | **wajib** | Path ke satu atau lebih file checkpoint (`.safetensors`). | | `--test` | str | `data/sample.txt` | Path ke file teks uji. | | `--device` | `cpu`/`cuda` | `cpu` | Device untuk evaluasi. | | `--guard` | str | `None` | Path file kamus; generate teks → hitung rasio kata dikenal. | | `--guard-max-new` | int | `120` | Jumlah token generate untuk evaluasi guard. | | `--seed` | int | `42` | Seed untuk generate saat `--guard` aktif. | | `--batch-size` | int | `32` | Batch size untuk evaluasi. | ### Contoh ```bash # Evaluasi satu checkpoint python eval.py --ckpt out/indigo_best.safetensors # Bandingkan beberapa checkpoint python eval.py --ckpt out/indigo_best.safetensors runs/*/ckpt/indigo.safetensors \ --test data/sample.txt # Dengan guard kamus python eval.py --ckpt out/indigo_best.safetensors --guard data/kampus_id.txt ``` ### Contoh output ``` set uji: data/sample.txt (29,123 karakter) checkpoint nats/tok nat/kar kamus run/csqa500/ckpt/indigo_best.safetensors 3.962 3.962 32% run/wiki500/ckpt/indigo_best.safetensors 3.018 3.018 25% ``` --- ## `audit_kamus.py` — Audit Wordlist Kamus Verifikasi kata-kata dalam file kamus terhadap KBBI daring (sampling acak). ### Argumen | Argumen | Tipe | Default | Deskripsi | |---|---|---|---| | `--kamus` | str | `data/kamus_id.txt` | Path ke file kamus (satu kata per baris). | | `--n` | int | `200` | Jumlah kata sampel yang diambil untuk verifikasi. | | `--seed` | int | `1337` | Seed random untuk sampling. | | `--delay` | float | `0.8` | Jeda antar-permintaan web (detik). Hindari rate limiting. | | `--backend` | `auto`/`pypi`/`web` | `auto` | Backend verifikasi. `pypi` = library kbbi, `web` = scraping kbbi.web.id. | | `--apply` | flag | `False` | Hapus kata TIDAK-DIKENAL dari kamus (backup otomatis ke `.bak.txt`). | | `--out` | str | `None` | Path output laporan JSON. Default: `runs/kamus_audit_.json`. | ### Contoh ```bash # Audit dasar python audit_kamus.py --kamus data/kamus_id.txt --n 200 # Hapus kata tak-valid (backup otomatis) python audit_kamus.py --kamus data/kamus_id.txt --apply # Backend web saja, delay lebih lama python audit_kamus.py --backend web --delay 1.5 --n 100 ``` ### Output Laporan JSON berisi: - `ada`: jumlah kata yang ditemukan di KBBI - `tidak_dikenal`: jumlah kata yang tidak ditemukan - `gagal_ceks`: jumlah verifikasi yang gagal (timeout, dll) - `estimasi_entri_tidak_valid`: proyeksi total entri tak-valid di seluruh kamus - `kata_ditolak`: daftar kata yang tidak dikenal (akan dihapus jika `--apply`) --- ## Guard Kamus (Sistem Kualitas Ejaan) Sistem guard membantu menjaga kualitas output model tanpa mengubah bobot: 1. **Wordlist**: `data/kamus_id.txt` — ~70K kata Indonesia (satu kata per baris) 2. **Morfologi**: `data/prefiks.txt` + `data/sufiks.txt` — pendeteksi imbuhan 3. **Asimilasi**: `meny-`→`s`, `peny-`→`s`, `pem-`→`p` — menangani perubahan bunyi Cara kerja: - **generate.py**: generate beberapa kandidat → pilih yang rasio kata dikenal tertinggi - **pipeline.py**: generate sekali → simpan `kamus_ratio` di manifest.json - **eval.py**: generate sekali → tampilkan rasio di tabel perbandingan Edit `data/prefiks.txt` dan `data/sufiks.txt` untuk memperluas cakupan tanpa menyentuh kode. --- ## Format Checkpoint | File | Isi | |---|---| | `*.safetensors` | Bobot model (format aman, tanpa pickle) | | `*_meta.json` | Metadata: config, vocab, step, val_loss, backend, tokenizer info | | `*_optimizer.pt` | State optimizer AdamW (untuk resume training) | Format `.safetensors` tidak mengeksekusi kode saat dimuat — lebih aman dari format `.pt` lama. --- ## Tips Training | Skenario | Rekomendasi | |---|---| | Data < 100 KB | `--tokenizer char`, 4L/4H/128E, 2000 steps | | Data 100 KB–1 MB | `--tokenizer char` atau `bpe` (vocab 512), 4L/8H/256E, 3000+ steps | | Data > 1 MB | `--tokenizer char` (BPE training sangat lambat di CPU >1MB) | | CPU-only | Model max ~1M params (4L/4H/128E), 5-8 detik/step | | Resume training | `--init-from runs/xxx/ckpt/indigo_best.safetensors` | --- ## Batasan - Dilatih pada data sangat kecil → output belum koheren; cocok untuk edukasi bukan produksi. - Gunakan checkpoint `indigo_best` (terpilih berdasarkan validasi), bukan checkpoint akhir. - BPE training sangat lambat di CPU untuk data > 1 MB — gunakan `--tokenizer char`. ## Keamanan Bobot disimpan sebagai `.safetensors` (tanpa pickle, tidak mengeksekusi kode saat dimuat). State optimizer (`*_optimizer.pt`) hanya untuk resume lokal — jangan dibagikan.