# Konsep kuantisasi

Sebuah angka terkuantisasi hanya dapat mengambil salah satu dari himpunan nilai kecil, yaitu 256 untuk int8 dan 16 untuk int4, sehingga setiap metode kuantisasi memperkenalkan kesalahan pembulatan. Metode-metode tersebut berbeda dalam cara mereka mencegah kesalahan tersebut merusak output model. Halaman ini menjelaskan pilihan di balik perbedaan-perbedaan tersebut: bagaimana angka dipetakan ke himpunan kecil tersebut, tensor mana yang dikuantisasi, tipe data mana yang digunakan, dan kapan kuantisasi terjadi.

Jika Anda baru dalam kuantisasi di Transformers, mulailah dengan [Ikhtisar](./overview).

## Petakan nilai ke bit yang lebih sedikit

Quantization memetakan rentang nilai presisi tinggi ke himpunan kecil bilangan bulat yang dapat direpresentasikan oleh tipe bit rendah. int8 dapat merepresentasikan 256 bilangan bulat, `[-128, 127]`, dan int4 dapat merepresentasikan 16.

*Affine quantization* adalah pemetaan yang paling umum. Metode ini mengambil nilai minimum dan maksimum dari sebuah tensor, lalu meregangkan rentang bilangan bulat di atasnya. Pada contoh di bawah, tensor dengan nilai yang berkisar dari -1,8 hingga 2,4 dipetakan ke int8, dan 0,0 jatuh pada -19.

```text
bf16  -1.8             0.0                    2.4
       ├────────────────┼──────────────────────┤
       ▼                ▼                      ▼
int8  -128             -19                    127
```

Dua parameter mendefinisikan pemetaan.

- *scale* `S` adalah ukuran satu langkah bilangan bulat dalam unit aslinya.

$$ S = \frac{val_{max} - val_{min}}{q_{max} - q_{min}} $$

- *Titik nol* `Z` adalah bilangan bulat yang merepresentasikan `0.0`.

$$ Z = q_{min} - round\left(\frac{val_{min}}{S}\right) $$

Nilai `x` dikuantisasi menjadi bilangan bulat `q` dengan membaginya dengan skala dan menggesernya dengan titik nol.

$$ q = round\left(\frac{x}{S} + Z\right) $$

Dequantizing membalikkan pemetaan dan memberikan kembali aproksimasi dari `x`. Perbedaan antara `x` dan aproksimasinya adalah kesalahan pembulatan.

$$ x \approx S \cdot (q - Z) $$

Pada contoh di atas, `S` sekitar 0.0165 dan `Z` adalah -19. Nilai 0.5 dikuantisasi menjadi 11, dan 11 didekuantisasi menjadi sekitar 0.494, dengan error pembulatan sekitar 0.006.

```text
┌─────┐  quantize   ┌────┐ dequantize   ┌───────┐
│ 0.5 │ ──────────▶ │ 11 │ ───────────▶ │ 0.494 │
└─────┘             └────┘              └───────┘
```

Kuantisasi afine hadir dalam dua bentuk.

- Kuantisasi *Asymmetric* menggunakan rumus di atas. Ia memetakan rentang yang tepat, sehingga tidak ada nilai yang dapat direpresentasikan yang terbuang ketika bobot tidak terpusat pada nol.
- Kuantisasi *Symmetric* mengasumsikan rentang adalah `[-a, a]` dan menetapkan `Z` ke 0, sehingga hanya skala yang disimpan. Perhitungannya lebih ringan, tetapi beberapa nilai yang dapat direpresentasikan tidak terpakai ketika bobot condong ke satu sisi nol.

## Pilih granularitas

Satu skala untuk seluruh tensor memang hemat untuk disimpan, tetapi satu nilai ekstrem yang besar akan memperlebar rentang dan menyebarkan nilai-nilai yang dapat direpresentasikan semakin jauh untuk setiap bobot lainnya. Kelompok yang lebih kecil memberikan setiap skala rentang yang lebih sempit untuk dicakup.

- Per-tensor menggunakan satu skala dan satu titik nol untuk seluruh tensor.
- Per-channel menggunakan satu skala untuk setiap kanal output, yaitu setiap baris dari matriks bobot.
- Per-group, atau per-block, menggunakan satu skala untuk setiap grup nilai berurutan, seperti 128 bobot. Setiap grup memerlukan penyimpanan tambahan untuk skalanya.

Diagram di bawah ini menunjukkan tiga opsi pada matriks bobot dengan tiga baris. Setiap `s` adalah satu skala.

```text
per-tensor        per-channel           per-group
┌────────────┐    ┌────────────┐        ┌──────┬──────┐
│            │    │            │ s0     │  s0  │  s1  │
│            │    ├────────────┤        ├──────┼──────┤
│     s      │    │            │ s1     │  s2  │  s3  │
│            │    ├────────────┤        ├──────┼──────┤
│            │    │            │ s2     │  s4  │  s5  │
└────────────┘    └────────────┘        └──────┴──────┘
```

Sebagian besar metode low-bit melakukan kuantisasi per grup. [GPTQ](./gptq) secara default menggunakan grup berisi 128 bobot, dan [Fine-grained FP8](./finegrained_fp8) secara default menggunakan blok 128x128. Format FP4 dalam [Data types](#data-types) membangun ukuran blok ke dalam format itu sendiri.

## Kuantisasi bobot atau aktivasi

Metode berbeda-beda dalam hal tensor mana yang dikuantisasi, dan hal ini menentukan di mana perkalian matriks dijalankan.

- Metode *Weight-only* menyimpan bobot dalam presisi rendah dan membiarkan aktivasi tetap dalam bf16 atau fp16. Sebelum setiap matmul, kernel melakukan dekuantisasi bobot ke dtype komputasi dengan presisi yang lebih tinggi. bitsandbytes 4-bit, AWQ, GPTQ, dan HQQ bekerja dengan cara ini. Di bitsandbytes, `bnb_4bit_compute_dtype` mengatur dtype komputasi tersebut.
- Metode *Weight-and-activation* juga mengkuantisasi aktivasi, dan matmul dijalankan langsung dalam presisi rendah. Hal ini memerlukan perangkat keras yang mendukung matmul presisi rendah, seperti FP8 pada GPU terbaru. Skema ini sering ditulis sebagai W8A8 untuk bobot dan aktivasi 8-bit.

Metode hanya-berat (weight-only) menghemat memori tetapi tidak selalu berjalan lebih cepat, karena dekuantisasi menambah beban kerja pada setiap matmul. Metode ini mempercepat generasi ketika model bersifat *memory-bound*. Saat menghasilkan satu token pada satu waktu, GPU menghabiskan sebagian besar setiap langkah untuk membaca berat dari memori, bukan untuk menghitung. Membaca berat 4-bit memindahkan seperempat dari data yang dilakukan oleh berat bf16, dan penghematan tersebut dapat melampaui biaya dekuantisasi.

Metode bobot-aktivasi membantu ketika model bersifat *compute-bound*, seperti pada prefill atau batch besar, karena matmul presisi rendah itu sendiri lebih cepat. Beberapa metode beralih di antara keduanya. NVFP4 menjalankan matmul bobot 4-bit, aktivasi 4-bit untuk prefill dan matmul bobot 4-bit, aktivasi 16-bit untuk decoding.

## Tipe data

Tipe data menentukan nilai-nilai yang dapat direpresentasikan oleh angka terkuantisasi.

### int8 dan int4

int8 dan int4 mewakili bilangan bulat yang berjarak sama dan menggunakan pemetaan afine di atas. int4 hanya dapat merepresentasikan 16 nilai, sehingga kehilangan lebih banyak akurasi dibandingkan int8. Metode yang menggunakan int4 biasanya melakukan kuantisasi per grup atau menggunakan kalibrasi untuk menjaga akurasi tetap tinggi.

Sebagian besar perangkat keras tidak memiliki tipe penyimpanan 4-bit, sehingga dua nilai int4 *dikemas* ke dalam satu byte. Misalnya, nilai pertama dapat menempati 4 bit bawah dan nilai kedua menempati 4 bit atas. Kernel mengurai nilai-nilai tersebut sebelum melakukan komputasi.

```text
┌──────┐  ┌──────┐        ┌──────┬──────┐
│ val1 │  │ val2 │        │ val2 │ val1 │
│ 0101 │  │ 0011 │   →    │ 0011 │ 0101 │  1 byte
└──────┘  └──────┘        └──────┴──────┘
  int4      int4            high   low
```

Beberapa tipe 4-bit mengatur nilai yang dapat diwakilinya secara tidak merata agar sesuai dengan distribusi bobot. bitsandbytes NF4 menempatkan lebih banyak nilainya di dekat nol, di mana sebagian besar bobot berada.

### FP8

FP8 mempertahankan tata letak bilangan titik apung yang terdiri dari tanda, eksponen, dan mantisa dalam 8 bit. Nilai-nilai yang dapat direpresentasikannya lebih berdekatan di sekitar nol dan semakin berjauhan pada magnitudo besar, yang sesuai dengan sebaran nilai pada bobot dan aktivasi.

FP8 hadir dalam dua varian.

- E4M3 memiliki 4 bit eksponen dan 3 bit mantisa. Format ini lebih presisi tetapi mencakup rentang yang lebih kecil. Ini adalah pilihan umum untuk bobot dan aktivasi selama inferensi.
- E5M2 memiliki 5 bit eksponen dan 2 bit mantisa. Format ini mencakup rentang yang lebih luas dengan presisi yang lebih rendah.

```text
E4M3   ┌─┬───────┬─────┐
       │S│E E E E│M M M│
       └─┴───────┴─────┘
E5M2   ┌─┬─────────┬───┐
       │S│E E E E E│M M│
       └─┴─────────┴───┘
  S sign   E exponent   M mantissa
```

Matmul FP8 memerlukan dukungan perangkat keras, seperti GPU NVIDIA dengan compute capability 8.9 atau lebih tinggi (Ada, Hopper, Blackwell), AMD Instinct MI300, dan Intel XPU. Transformers mendukung FP8 melalui [Fine-grained FP8](./finegrained_fp8), [FBGEMM FP8](./fbgemm_fp8), dan [compressed-tensors](./compressed_tensors).

### FP4 dengan skala blok

FP4 memiliki 1 bit tanda, 2 bit eksponen, dan 1 bit mantisa (E2M1). Format ini hanya merepresentasikan nilai 0, 0,5, 1, 1,5, 2, 3, 4, dan 6, baik positif maupun negatif. Dengan begitu sedikit nilai yang tersedia, format FP4 tidak dapat bekerja dengan satu skala per tensor. Skala ditempelkan pada setiap blok kecil nilai dan skala tersebut disimpan sebagai bagian dari format.

| Format | Ukuran blok | Skala per blok | Skala tambahan |
|---|---|---|---|
| [MXFP4](./mxfp4) | 32 nilai | 8-bit pangkat dua (E8M0) | None |
| [NVFP4](./nvfp4) | 16 nilai | FP8 | Satu skala FP32 per tensor |

MXFP4 adalah format microscaling. Skala pangkat dua hanya menggeser eksponen, sehingga penerapannya murah, tetapi hanya dapat mendarat pada pangkat dua. NVFP4 menggunakan blok yang lebih kecil dan skala FP8 yang dapat mengambil nilai di antara pangkat dua. Hal ini mempertahankan lebih banyak akurasi, dengan biaya berupa skala per-tensor yang menjaga skala blok FP8 tetap dalam rentang. [Four Over Six](./fouroversix) dan [FP-Quant](./fp_quant) juga menghasilkan bobot NVFP4 atau MXFP4.

Ide blok yang sama juga muncul pada 8 bit. MXFP8 menyimpan nilai FP8 dengan skala blok pangkat dua, dan [Fine-grained FP8](./finegrained_fp8) memuatnya.

### Codebooks

Di bawah 4 bit, beberapa metode berhenti membulatkan setiap bobot ke nilai terdekat yang dapat direpresentasikan. Mereka melakukan kuantisasi pada kelompok bobot secara bersama-sama sebagai *vektor*. Codebook menyimpan seperangkat vektor yang telah dipelajari, dan setiap kelompok bobot disimpan sebagai indeks dari vektor terdekatnya. [AQLM](./aqlm) secara default menggunakan kelompok 8 bobot dan indeks 16-bit, yang setara dengan 2 bit per bobot. [VPTQ](./vptq) juga menggunakan codebook. Menemukan codebook yang baik memerlukan optimasi yang panjang pada data kalibrasi, sehingga model-model ini dikuantisasi terlebih dahulu dan dimuat sebagai checkpoint.

## Ketika kuantisasi terjadi

Sebuah model dikuantisasi baik setelah pelatihan maupun selama proses pelatihan. Sebagian besar metode di Transformers melakukan kuantisasi setelah pelatihan.

### Kuantisasi pasca-pelatihan

*Kuantisasi pasca-pelatihan (PTQ)* mengkuantisasi model yang sudah dilatih, tanpa mengubah apa yang telah dipelajarinya. Metode PTQ berbeda dalam hal apakah memerlukan data atau tidak, dan hal ini menentukan ke dalam kelompok mana suatu metode diklasifikasikan dalam daftar isi dokumentasi.

- Tanpa data kalibrasi, skala berasal hanya dari bobot, dan kuantisasi cukup cepat untuk dijalankan saat model dimuat. bitsandbytes, HQQ, dan torchao bekerja dengan cara ini. Metode-metode ini tercantum di bawah *Kuantisasi saat memuat*.
- Dengan data kalibrasi, metode ini menjalankan dataset kecil melalui model dan menggunakan aktivasi untuk menentukan cara mengkuantisasi setiap layer. Kalibrasi biasanya memberikan akurasi yang lebih baik pada 4 bit dan di bawahnya, tetapi membutuhkan waktu dari beberapa menit hingga beberapa jam.

Metode kalibrasi menggunakan aktivasi dengan cara yang berbeda. GPTQ mengkuantisasi lapisan satu kolom pada satu waktu dan menyesuaikan kolom yang tersisa untuk mengompensasi kesalahan pembulatan dari setiap kolom. AWQ menemukan kanal bobot dengan aktivasi terbesar dan memperbesarnya sebelum pembulatan untuk melindunginya.

Di mana jumlah eksekusi kalibrasi menentukan cara Anda menggunakan metode tersebut di Transformers.

- [GPTQ](./gptq) melakukan kalibrasi di dalam `from_pretrained` ketika Anda meneruskan `dataset` ke [GPTQConfig](/docs/transformers/main/id/main_classes/quantization#transformers.GPTQConfig), sehingga dicantumkan di bawah *Kuantisasi saat memuat*.
- Metode kalibrasi lain, seperti AWQ, AutoRound, AQLM, dan VPTQ, melakukan kalibrasi di pustaka mereka sendiri. Transformers memuat hasilnya, sehingga metode-metode tersebut tercantum di bawah *Memuat checkpoint yang sudah dikuantisasi*.

### Pelatihan yang mempertimbangkan kuantisasi

*Quantization-aware training (QAT)* mensimulasikan kuantisasi selama pelatihan. Pada forward pass, nilai dibulatkan seperti yang dilakukan kuantisasi, sementara gradien memperbarui bobot berpresisi penuh, sehingga model belajar mentoleransi kesalahan pembulatan. QAT biasanya memberikan akurasi terbaik pada lebar bit yang sangat rendah, tetapi memerlukan pelatihan model.

Model [BitNet](./bitnet) dilatih dengan cara ini, dengan bobot 1,58-bit mereka dalam loop, dan Transformers memuat checkpoint yang telah dilatih.

## Sumber daya

- [Dasar-Dasar Kuantisasi dengan Hugging Face](https://www.deeplearning.ai/short-courses/quantization-fundamentals-with-hugging-face/)
- [Quantisasi Secara Mendalam](https://www.deeplearning.ai/short-courses/quantization-in-depth)
- [Pengenalan Quantization yang dimasak di 🤗 dengan 💗🧑‍🍳](https://huggingface.co/blog/merve/quantization)
- [Kuliah EfficientML.ai 5 - Kuantisasi Bagian I](https://www.youtube.com/watch?v=RP23-dRVDWM)
- [Membuat Deep Learning Berjalan Lancar dari Prinsip Dasar](https://horace.io/brrr_intro.html)
- [Mempercepat Generative AI dengan PyTorch Bagian 2: Optimasi LLM](https://pytorch.org/blog/accelerating-generative-ai-2/)

