ai-experiment/CLASSIFY.md

17 KiB
Raw Blame History

Panduan Classification Runner (Laya / laya.cpp)

Dokumen ini menjelaskan cara pakai classify_runner.py — runner klasifikasi teks pakai Laya, model keputusan (decision model) multilingual 322M parameter, yang dijalankan lewat runtime native laya.cpp di CPU.

Untuk ringkasan proyek dan setup runner lain, lihat README.md.


Daftar isi

  1. Cara kerja singkat
  2. Menjalankan
  3. Cara baca output
  4. Tiga tipe pertanyaan
  5. Pertanyaan sendiri
  6. Mode server (lebih cepat)
  7. Output JSON
  8. Semua opsi CLI
  9. Konfigurasi
  10. Setup dari nol
  11. Catatan penting

1. Cara kerja singkat

Laya bukan classifier satu-label biasa. Kamu memberi dia sebuah state (teks, email, ticket, atau JSON) plus beberapa pertanyaan bertipe. Tiap pertanyaan mendeklarasikan ruang jawabannya sendiri, jadi tidak perlu training/finetune untuk memakainya di kasus baru.

Semua pertanyaan dijawab dalam satu forward pass, dengan probabilitas — jadi tidak ada teks yang perlu diparse dan tidak ada risiko hallucination seperti pada LLM generatif.

state + pertanyaan  →  satu forward pass  →  jawaban bertipe + probabilitas

Runner mendukung tiga mode pemanggilan:

Mode Kapan dipakai Kondisi Kecepatan
CLI satu kali jalan tanpa setup ~5–7 detik
Server dipakai berulang harus nyalakan server ~0,6 detik

2. Menjalankan

Cara paling dasar

./usage-classify.sh "Saya dikenakan biaya dua kali untuk invoice 4411, tolong kembalikan uang saya."

Kalau teksnya panjang, pakai file

./usage-classify.sh --state-file ticket.txt

Kalau state-nya punya beberapa kolom

Kalau teks diawali { atau [, runner otomatis mengirimnya sebagai struktur JSON, bukan teks biasa. Jadi email dengan kolom subject/body bisa langsung:

./usage-classify.sh '{"subject":"Refund not received","body":"I cancelled two weeks ago and still have no refund."}'

Atau dari file JSON:

./usage-classify.sh --state-file ticket.json

3. Cara baca output

Contoh output nyata:

State     : Saya dikenakan biaya dua kali untuk invoice 4411, tolong kembalikan uang saya.
Questions : 3 (cli)
Backend   : CPU (12th Gen Intel(R) Core(TM) i7-12700H)
Elapsed   : 6.953 s
Inference : 0.732 s
Tokens    : 176 in / 0 out

department : billing
             billing=1.0000  technical=0.0000  sales=0.0000  other=0.0000
urgency    : 1.7297  (immediate)
             0=0.0306  1=0.2091  2=0.7603
refund     : 0.7751
             act_probability=1.0000

Bagian atas

Baris Arti
State State yang dikirim (dipotong 120 karakter kalau panjang)
Questions Berapa pertanyaan dijawab, dan mode yang dipakai (cli atau server)
Backend Backend dan perangkat yang dipakai. Hanya muncul di mode CLI
Elapsed Total waktu wall-clock, termasuk load bobot model
Inference Waktu forward pass saja. Hanya muncul di mode CLI
Tokens Jumlah token input yang diproses (output selalu 0, karena model ini tidak generatif)

Kenapa Elapsed jauh lebih besar dari Inference? Model ini tidak generatif, jadi forward pass-nya cuma ~0,6 detik. Sisanya (~5 detik) adalah waktu baca bobot 644MB dari disk. Ini normal. Kalau kamu pemakai berulang, pakai mode server.

Bagian jawaban

Nama di kiri adalah ID pertanyaan yang kamu tentukan sendiri. Formatnya berbeda per tipe:

choice — jawaban berupa salah satu label. Baris kedua menampilkan probabilitas tiap opsi, diurutkan dari yang tertinggi:

department : billing
             billing=1.0000  technical=0.0000  sales=0.0000  other=0.0000

score — jawaban berupa angka desimal (rata-rata tertimbang dari distribusi), diikuti label level yang paling dekat dalam kurung. Baris kedua menampilkan probabilitas tiap level dalam urutan legend (tidak diurutkan):

urgency    : 1.7297  (immediate)
             0=0.0306  1=0.2091  2=0.7603

noul — jawaban berupa peluang pernyataan itu benar (0.0 = pasti salah, 1.0 = pasti benar). Tipe ini tidak punya objek probabilities, jadi baris kedua menampilkan act_probability, yaitu peluang bahwa kasus ini perlu di-escalate ke manusia:

refund     : 0.7751
             act_probability=1.0000

4. Tiga tipe pertanyaan

Tipe Isi criteria Output Kapan pakai
choice objek label → deskripsi label terpilih + peluang tiap label klasifikasi ke kategori
score array berurutan angka desimal + label level intensitas, prioritas, sentimen
noul tidak ada peluang pernyataan true deteksi ya/tidak

Contoh lengkap ketiga tipe (ini isi preset triage di config/classify.py):

{
  "department": {
    "type": "choice",
    "instructions": "Which team should handle this?",
    "criteria": {
      "billing": "invoices, payments, refunds",
      "technical": "bugs, outages, system errors",
      "sales": "pricing, plans, new contracts",
      "other": "everything else"
    }
  },
  "urgency": {
    "type": "score",
    "instructions": "How urgent is the request?",
    "criteria": ["not urgent", "soon", "immediate"]
  },
  "refund": {
    "type": "noul",
    "instructions": "Does the customer ask for money back?"
  }
}

Aturan penting

  • choice jangan lebih dari ~20 opsi. Semua opsi berbagi satu budget token yang tetap, jadi kalau label terlalu banyak, tiap label cuma kebagian sedikit token dan akurasinya jatuh tajam.
  • score pakai array, bukan objek. Urutan array itu penting — itulah yang menentukan level 0, 1, 2, dan seterusnya.
  • instructions ditulis dalam bahasa Inggris walau isinya bisa bahasa apa pun. Instruksi bagian kepala (256 token) dan state diletakkan terpisah oleh [SEP] dalam model, jadi instruksi tidak ikut mengotori bahasa input.
  • Nama kolom output bebas kamu pilih. Kolom di kiri output itu key yang kamu pakai di objek questions, bukan dari instructions.

Multibahasa

Checkpoint-nya memang varian multilingual (100+ bahasa), jadi input non-Inggris diharapkan berfungsi:

$ ./usage-classify.sh "二重に請求されました。返金をお願いします。"
refund     : 0.9976

$ ./usage-classify.sh "双重收费了,请退款。"
refund     : 0.9892

5. Pertanyaan sendiri

Preset triage cuma comprise 3 pertanyaan default. Untuk kasus kamu, bikin file JSON:

cat > my_questions.json <<'EOF'
{
  "bahasa": {
    "type": "choice",
    "instructions": "What language is this text written in?",
    "criteria": {
      "id": "Indonesian",
      "en": "English",
      "fr": "French",
      "zh": "Chinese"
    }
  },
  "komplain": {
    "type": "noul",
    "instructions": "Does the customer complain or express dissatisfaction?"
  },
  "sentimen": {
    "type": "score",
    "instructions": "How positive is the tone?",
    "criteria": ["very negative", "neutral", "positive", "very positive"]
  }
}
EOF

./usage-classify.sh --questions my_questions.json "produk ini error terus sejak kemarin"

File-nya boleh berisi objek questions langsung (seperti di atas), atau objek request lengkap yang punya key "questions". Runner mendeteksi keduanya.

Kamu juga bisa simpan pertanyaan sebagai preset permanen di config/classify.py supaya bisa dipakai cuma dengan --preset nama:

PRESETS = {
    "triage": { ... },      # bawaan
    "bahasa": { ... },      # preset kamu
}
DEFAULT_PRESET = "triage"
./usage-classify.sh --preset bahasa "produk ini error terus sejak kemarin"

6. Mode server (lebih cepat)

Tiap kali runner dipanggil dalam mode CLI, ia me-spawn laya-cli dan load ulang bobot 644MB dari disk. Itu yang bikin total ~5–7 detik, padahal inferensinya cuma 0,6 detik.

Kalau kamu classifies berulang, biarkan model tetap resident di memory lewat HTTP server bawaan laya.cpp:

third_party/laya.cpp/build-cpu/bin/laya-cli --server --port 8080 \
    --model models/convaiinnovations/laya --variant multilingual --cpu &

Lalu arahkan runner ke sana:

LAYA_URL=http://127.0.0.1:8080 ./usage-classify.sh "teks kamu"

Hasilnya ~0,6 detik bukan ~6 detik — sekitar 8x lebih cepat, dengan jawaban yang identik (bisa diverifikasi langsung: bandingkan output CLI dan server untuk input yang sama).

Urutan prioritas URL

  1. --url di CLI
  2. environment variable LAYA_URL
  3. LAYA_URL di config/classify.py

Kalau server mati

Runner otomatis fallback ke mode CLI, dengan informasi di stderr:

laya.cpp server at http://127.0.0.1:8080 unavailable (Connection refused). Falling back to the CLI.

Jadi tidak ada risiko kalau lupa nyalakan server atau server-nya crash.

Endpoint lain yang tersedia

Server laya.cpp juga menyediakan POST /predict (bisa batch beberapa request sekaligus, dan mengembalikan envelope CLI lengkap termasuk elapsed_ms), GET /health, dan GET /v1/models. Runner memakai /v1/systemone yang bentuk balasannya lebih ringkas.


7. Output JSON

./usage-classify.sh "teks" --json

Menghasilkan envelope mentah laya.cpp dengan probabilitas presisi penuh (bukan 4 desimal) — berguna kalau mau pipe ke program lain:

{
  "results": [
    {
      "model": "laya-rl-agent",
      "answers": {
        "refund": {
          "type": "noul",
          "confidence": 0.7751,
          "action": { "act_probability": 1.0 },
          "noul": 0.7751
        }
      },
      "usage": { "input_tokens": 176, "output_tokens": 0 }
    }
  ],
  "elapsed_ms": 732.14,
  "backend": "CPU",
  "device": "12th Gen Intel(R) Core(TM) i7-12700H"
}

Bentuk tiap tipe jawaban:

Tipe Field yang ada
choice type, choice, probabilities, confidence, action.act_probability
score type, score, legend, probabilities, confidence, action.act_probability
noul type, noul, confidence, action.act_probability

8. Semua opsi CLI

usage: classify_runner.py [-h] [--state-file STATE_FILE] [--preset PRESET]
                          [--questions QUESTIONS] [--url URL] [--json]
                          [state]

positional arguments:
  state                 State yang akan diklasifikasi (teks biasa atau JSON).
                        Hilangkan kalau mau baca dari --state-file.

options:
  --state-file STATE_FILE   File yang berisi state (teks biasa atau JSON)
  --preset PRESET           Nama preset pertanyaan dari config/classify.py
  --questions QUESTIONS     File JSON berisi objek questions, mengoverride --preset
  --url URL                 Base URL server laya.cpp, mengoverride LAYA_URL dan config
  --json                    Cetak envelope respons mentah sebagai JSON
  -h, --help                Tampilkan bantuan

Catatan: --state bukan sebuah flag — state diberikan sebagai argumen posisi. Writer --state akan menghasilkan error, bukan diam-diam diperlakukan sebagai nama file.

Exit code:

Kode Arti
0 Berhasil
1 Gagal — preset tidak dikenal, file tidak ada, state kosong, atau inference error

Contoh pemakaian error yang umum:

$ ./usage-classify.sh --preset salah "teks"
Unknown preset: salah. Available: triage

$ ./usage-classify.sh
Usage: ./usage-classify.sh <state> [--preset <name>] [--questions <file.json>] [--json]

9. Konfigurasi

Semua default ada di config/classify.py:

LAYACPP      = <repo>/third_party/laya.cpp
CLI          = LAYACPP / "build-cpu" / "bin" / "laya-cli"

MODEL_ROOT   = MODEL_DIR / "convaiinnovations" / "laya"
VARIANT      = "multilingual"
CHECKPOINT   = MODEL_ROOT / VARIANT / "model.safetensors"

BACKEND      = "cpu"          # cpu | cuda | vulkan | coreml
ALLOW_TRUNCATION = False
LAYA_URL     = ""             # "" = spawn CLI per jalan
TIMEOUT      = 120

PRESETS      = { "triage": {...} }
DEFAULT_PRESET = "triage"
Variabel Fungsi
CLI Lokasi binary laya-cli hasil build
MODEL_ROOT Direktori induk checkpoint
VARIANT Varian checkpoint: multilingual, english, atau typed-decisions
BACKEND Backend komputasi. cpu = pure CPU, tanpa GPU sama sekali
ALLOW_TRUNCATION Kalau True, input kepanjangan dipotong diam-diam. Default: request ditolak
LAYA_URL Fallback URL server kalau env dan flag tidak di-set
PRESETS Kumpulan pertanyaan siap pakai
DEFAULT_PRESET Preset yang dipakai kalau --preset tidak diberikan

Ganti ke GPU nanti

Ubah BACKEND jadi "cuda" atau "vulkan". Tapi kamu harus pakai binary laya.cpp yang dikompilasi dengan backend tersebut — build CPU-only (yang sekarang) tidak bisa. Detail di README.md.


10. Setup dari nol

Di mesin ini sudah selesai. Kalau perlu diulang (misal di mesin lain):

./build-laya.sh

Script-nya tidak butuh sudo sama sekali:

  • cmake dan ninja diambil dari pip wheel
  • nlohmann/json diekstrak ke prefix lokal third_party/nlohmann-install
  • satu-satunya kebutuhan sistem adalah libicu-dev

Script aman dijalankan berulang — setiap langkah dilewati kalau output-nya sudah ada.

Yang dilakukan script:

  1. Clone laya.cpp beserta submodulnya (ggml, cpp-httplib) ke third_party/laya.cpp
  2. Pasang cmake (versi 3.x) dan ninja lewat pip kalau belum ada
  3. Ekstrak nlohmann/json ke prefix lokal
  4. Build CPU-only ke third_party/laya.cpp/build-cpu

Tiga hal yang wajib

Flag Kenapa wajib
-DLAYA_CUDA=OFF Default-nya ON, dan konfigurasi akan gagal mencari nvcc
cmake versi 3.x ggml yang di-pin laya.cpp deklarasikan cmake_minimum_required(3.14...3.28) dan tidak bisa dikonfigurasi di CMake 4
--cpu saat runtime Default laya-cli adalah backend CUDA, jadi build CPU-only akan gagal start tanpa flag ini

Kenapa harus build dari sumber?

Upstream laya.cpp tidak menyediakan binary prebuilt khusus CPU — rilis Linux hanya CUDA 12, CUDA 13, dan Vulkan. Kalau di mesin dengan GPU NVIDIA, kamu bisa unduh binary prebuilt dan menghemat langkah build, tapi build CPU-only ini nol dependensi GPU.

Struktur checkpoint

Checkpoint ada di models/convaiinnovations/laya/multilingual/.

Penting: laya-cli menambahkan nama varian ke path --model untuk semua varian non-English. Jadi checkpoint multilingual harus berada di <MODEL_ROOT>/multilingual/.

Ada salinan bobot yang identik di models/convaiinnovations/laya-multilingual/, tapi layout itu tidak bisa dipakai laya.cpp — direktori itu untuk implementasi referensi Python.


11. Catatan penting

Dua keterbatasan dari model card upstream yang belum diatasi runner ini. Keduanya penting sebelum kamu mempercayai angkanya:

1. Probabilitasnya belum dikalibrasi

Model ini di-ship uncalibrated dengan temperature = [1.0, 1.0, 1.0] tanpa bucket per jumlah opsi. Akibatnya dia sistematis over-confident — rata-rata confidence 0,75–0,83 padahal akurasinya jauh lebih rendah.

Jadi nilai confidence=1.0 di output belum tentu akurat.

Untuk membaikkannya, refit satu temperatur per kombinasi (tipe pertanyaan, jumlah opsi) di data held-out milikmu sendiri. Dari model card, ini memindahkan mean ECE dari 0,314 → 0,106. Lakukan ini sebelum memperlakukan probabilitas sebagai ambang keputusan.

2. Tipe noul bisa under-report true

Pada input yang jelas positif, satu pengukuran menempatkan P(true) di kisaran 0,5 sementara kasus negatifnya benar-benar dekat 0. Kalau jawaban noul terasa lemah, cek ulang dengan versi dua opsi:

{
  "cek": {
    "type": "choice",
    "instructions": "Does the text ask for a refund?",
    "criteria": {
      "A": "no, it does not ask for a refund",
      "B": "yes, it asks for a refund"
    }
  }
}

3. Bahasa dengan sumber daya rendah masih lemah

SWE, Tamil, Amharic masih berada di rentang 0,110–0,250. Check akurasi di bahasa-bahasa yang memang kamu pakai, jangan diasumsikan seragam.

4. Model multilingual lebih lemah untuk teks Inggris

Makro accuracy 0,619 (multilingual) vs 0,684 (checkpoint Inggris). Kalau mostly English, routing ke checkpoint laya (Inggris) lebih akurat daripada memaksakan pakai multilingual.


Ringkasan perintah

# Klasifikasi teks
./usage-classify.sh "teks kamu"

# Dari file
./usage-classify.sh --state-file ticket.txt

# Pertanyaan sendiri
./usage-classify.sh --questions my_questions.json "teks kamu"

# Output JSON untuk di-pipe
./usage-classify.sh "teks kamu" --json

# Mode cepat: nyalakan server sekali
third_party/laya.cpp/build-cpu/bin/laya-cli --server --port 8080 \
    --model models/convaiinnovations/laya --variant multilingual --cpu &
LAYA_URL=http://127.0.0.1:8080 ./usage-classify.sh "teks kamu"

# Setup (sekali saja, tanpa sudo)
./build-laya.sh