# Conceitos de quantização

Um número quantizado só pode assumir um de um pequeno conjunto de valores, 256 para int8 e 16 para int4, portanto, todo método de quantização introduz erro de arredondamento. Os métodos diferem em como evitam que esse erro prejudique a saída do modelo. Esta página explica as escolhas por trás dessas diferenças: como os números são mapeados para esse pequeno conjunto, quais tensores são quantizados, quais tipos de dados são usados e quando a quantização ocorre.

Se você está começando a usar quantização no Transformers, comece pela [Visão geral](./overview).

## Mapear valores para menos bits

A quantização mapeia um intervalo de valores de alta precisão para o pequeno conjunto de inteiros que um tipo de baixo bit pode representar. O int8 pode representar 256 inteiros, `[-128, 127]`, e o int4 pode representar 16.

*Quantização afim* é o mapeamento mais comum. Ele pega os valores mínimo e máximo de um tensor e estica o intervalo de inteiros sobre ele. No exemplo abaixo, um tensor cujos valores variam de -1,8 a 2,4 é mapeado para int8, e 0,0 cai em -19.

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

Dois parâmetros definem o mapeamento.

- O *scale* `S` é o tamanho de um passo inteiro nas unidades originais.

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

- O *ponto zero* `Z` é o inteiro que representa `0.0`.

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

Um valor `x` é quantizado para o inteiro `q` dividindo pela escala e deslocando pelo ponto zero.

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

A desquantização inverte o mapeamento e devolve uma aproximação de `x`. A diferença entre `x` e sua aproximação é o erro de arredondamento.

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

No exemplo acima, `S` é aproximadamente 0.0165 e `Z` é -19. O valor 0.5 é quantizado para 11, e 11 é desquantizado para aproximadamente 0.494, um erro de arredondamento de cerca de 0.006.

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

A quantização afim vem em duas formas.

- A quantização *Assimétrica* usa as fórmulas acima. Ela mapeia o intervalo exato, de modo que nenhum valor representável é desperdiçado quando os pesos não estão centralizados em zero.
- A quantização *Symmetric* pressupõe que o intervalo seja `[-a, a]` e fixa `Z` em 0, de modo que apenas a escala é armazenada. O cálculo é mais barato, mas alguns valores representáveis ficam sem uso quando os pesos se inclinam para um lado do zero.

## Escolha a granularidade

Uma única escala para um tensor inteiro é barata de armazenar, mas um único valor atípico grande estende o intervalo e afasta os valores representáveis para todos os outros pesos. Grupos menores dão a cada escala um intervalo mais estreito para cobrir.

- Per-tensor usa uma única escala e um único ponto zero para o tensor inteiro.
- Per-channel usa uma escala para cada canal de saída, que corresponde a cada linha de uma matriz de pesos.
- Por grupo, ou por bloco, usa uma escala para cada grupo de valores consecutivos, como 128 pesos. Cada grupo exige armazenamento extra para sua escala.

O diagrama abaixo mostra as três opções em uma matriz de pesos com três linhas. Cada `s` é uma escala.

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

A maioria dos métodos de baixo número de bits realiza a quantização por grupo. [GPTQ](./gptq) usa, por padrão, grupos de 128 pesos, e [Fine-grained FP8](./finegrained_fp8) usa, por padrão, blocos de 128x128. Os formatos FP4 em [Data types](#data-types) incorporam o tamanho do bloco ao próprio formato.

## Quantizar pesos ou ativações

Os métodos diferem em quais tensores eles quantizam, e isso determina onde a multiplicação de matrizes é executada.

- Métodos *Weight-only* armazenam os pesos em baixa precisão e mantêm as ativações em bf16 ou fp16. Antes de cada matmul, o kernel desquantiza os pesos para um dtype de computação de maior precisão. bitsandbytes 4-bit, AWQ, GPTQ e HQQ funcionam dessa forma. No bitsandbytes, `bnb_4bit_compute_dtype` define esse dtype de computação.
- Os métodos de *pesos-e-ativações* também quantizam as ativações, e o matmul é executado diretamente em baixa precisão. Isso requer hardware que suporte o matmul de baixa precisão, como FP8 em GPUs recentes. O esquema é frequentemente escrito como W8A8 para pesos e ativações de 8 bits.

Métodos apenas de pesos economizam memória, mas nem sempre executam mais rápido, pois a desquantização adiciona trabalho a cada matmul. Eles aceleram a geração quando o modelo é *limitado pela memória*. Ao gerar um token de cada vez, uma GPU passa a maior parte de cada etapa lendo pesos da memória, em vez de calcular. Ler pesos de 4 bits move um quarto dos dados que pesos de bf16 movem, e essa economia pode superar o custo da desquantização.

Métodos de pesos e ativações são úteis quando o modelo é *limitado por computação*, como em prefill ou lotes grandes, pois o próprio matmul de baixa precisão é mais rápido. Alguns métodos alternam entre os dois. O NVFP4 executa um matmul com pesos de 4 bits e ativações de 4 bits para prefill e um matmul com pesos de 4 bits e ativações de 16 bits para decodificação.

## Tipos de dados

O tipo de dados define quais valores um número quantizado pode representar.

### int8 e int4

int8 e int4 representam inteiros igualmente espaçados e utilizam o mapeamento afim acima. int4 pode representar apenas 16 valores, portanto, perde mais precisão do que int8. Os métodos que utilizam int4 geralmente quantizam por grupo ou usam calibração para manter a precisão.

A maioria dos hardwares não possui um tipo de armazenamento de 4 bits, portanto, dois valores int4 são *empacotados* em um único byte. Por exemplo, o primeiro valor pode ocupar os 4 bits inferiores e o segundo valor os 4 bits superiores. Os kernels desempacotam os valores antes de realizar os cálculos com eles.

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

Alguns tipos de 4 bits distribuem seus valores representáveis de forma desigual para corresponder à forma como os pesos são distribuídos. O NF4 do bitsandbytes coloca mais de seus valores perto de zero, onde a maioria dos pesos se encontra.

### FP8

O FP8 mantém o layout de ponto flutuante com sinal, expoente e mantissa em 8 bits. Seus valores representáveis estão mais próximos uns dos outros perto de zero e mais espaçados em magnitudes grandes, o que se adequa à distribuição de valores em pesos e ativações.

O FP8 vem em duas variantes.

- E4M3 possui 4 bits de expoente e 3 bits de mantissa. É mais preciso, mas cobre um intervalo menor. É a escolha usual para pesos e ativações durante a inferência.
- E5M2 possui 5 bits de expoente e 2 bits de mantissa. Ele cobre uma faixa mais ampla com menos precisão.

```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
```

Os matmuls FP8 exigem suporte de hardware, como GPUs NVIDIA com capacidade de computação 8.9 ou superior (Ada, Hopper, Blackwell), AMD Instinct MI300 e Intel XPU. O Transformers oferece suporte a FP8 por meio de [Fine-grained FP8](./finegrained_fp8), [FBGEMM FP8](./fbgemm_fp8) e [compressed-tensors](./compressed_tensors).

### FP4 com escalas de bloco

O FP4 possui 1 bit de sinal, 2 bits de expoente e 1 bit de mantissa (E2M1). Ele representa apenas 0, 0.5, 1, 1.5, 2, 3, 4 e 6, tanto positivos quanto negativos. Com tão poucos valores, os formatos FP4 não funcionam com uma única escala por tensor. Eles associam uma escala a cada pequeno bloco de valores e armazenam as escalas como parte do formato.

| Formato | Tamanho do bloco | Escala por bloco | Escala extra |
|---|---|---|---|
| [MXFP4](./mxfp4) | 32 valores | 8 bits potência de dois (E8M0) | None |
| [NVFP4](./nvfp4) | 16 valores | FP8 | Uma escala FP32 por tensor |

MXFP4 é um formato de microescala. Uma escala de potência de dois apenas desloca o expoente, o que torna sua aplicação barata, mas ela só pode resultar em potências de dois. O NVFP4 usa blocos menores e uma escala FP8 que pode assumir valores entre potências de dois. Isso preserva mais precisão, ao custo de uma escala por tensor que mantém as escalas de bloco FP8 dentro do intervalo. [Four Over Six](./fouroversix) e [FP-Quant](./fp_quant) também produzem pesos NVFP4 ou MXFP4.

A mesma ideia de blocos aparece em 8 bits. O MXFP8 armazena valores FP8 com escalas de bloco em potências de dois, e o [Fine-grained FP8](./finegrained_fp8) o carrega.

### Codebooks

Abaixo de 4 bits, alguns métodos deixam de arredondar cada peso para o valor representável mais próximo. Eles quantizam grupos de pesos juntos como *vetores*. Um codebook armazena um conjunto de vetores aprendidos, e cada grupo de pesos é armazenado como o índice do vetor mais próximo. O [AQLM](./aqlm) usa, por padrão, grupos de 8 pesos e um índice de 16 bits, o que resulta em 2 bits por peso. O [VPTQ](./vptq) também utiliza codebooks. Encontrar bons codebooks exige uma otimização longa sobre dados de calibração, portanto, esses modelos são quantizados previamente e carregados como checkpoints.

## Quando a quantização ocorre

Um modelo é quantizado após o treinamento ou durante ele. A maioria dos métodos em Transformers quantiza após o treinamento.

### Quantização pós-treino

*Quantização pós-treinamento (PTQ)* quantiza um modelo que já foi treinado, sem alterar o que ele aprendeu. Os métodos de PTQ diferem em relação à necessidade de dados, e isso determina em qual grupo um método se enquadra na tabela de conteúdo da documentação.

- Sem dados de calibração, as escalas derivam apenas dos pesos, e a quantização é rápida o suficiente para ser executada durante o carregamento do modelo. bitsandbytes, HQQ e torchao funcionam dessa forma. Esses métodos estão listados em *Quantizar durante o carregamento*.
- Com dados de calibração, o método executa um pequeno conjunto de dados no modelo e usa as ativações para decidir como quantizar cada camada. A calibração geralmente oferece melhor precisão em 4 bits ou menos, mas leva de minutos a horas.

Os métodos de calibração utilizam as ativações de diferentes maneiras. O GPTQ quantiza uma camada coluna por coluna e ajusta as colunas restantes para compensar o erro de arredondamento de cada coluna. O AWQ identifica os canais de pesos com as maiores ativações e os escala para cima antes do arredondamento para protegê-los.

O local em que a execução de calibração é realizada determina como você usa o método no Transformers.

- [GPTQ](./gptq) realiza a calibração dentro de `from_pretrained` quando você passa um `dataset` para [GPTQConfig](/docs/transformers/main/pt/main_classes/quantization#transformers.GPTQConfig), por isso está listado em *Quantize while loading*.
- Outros métodos de calibração, como AWQ, AutoRound, AQLM e VPTQ, realizam a calibração em suas próprias bibliotecas. O Transformers carrega o resultado, por isso eles estão listados em *Carregar checkpoints pré-quantizados*.

### Treinamento com quantização

*Quantization-aware training (QAT)* simula a quantização durante o treinamento. A passagem para frente (forward pass) arredonda os valores da mesma forma que a quantização faria, enquanto os gradientes atualizam os pesos em precisão completa, permitindo que o modelo aprenda a tolerar o erro de arredondamento. O QAT geralmente oferece a melhor precisão em larguras de bits muito baixas, mas exige o treinamento do modelo.

Os modelos [BitNet](./bitnet) são treinados dessa forma, com seus pesos de 1,58 bits no loop, e o Transformers carrega os checkpoints treinados.

## Recursos

- [Fundamentos de Quantização com Hugging Face](https://www.deeplearning.ai/short-courses/quantization-fundamentals-with-hugging-face/)
- [Quantização em profundidade](https://www.deeplearning.ai/short-courses/quantization-in-depth)
- [Introdução à Quantização cozinhada no 🤗 com 💗🧑‍🍳](https://huggingface.co/blog/merve/quantization)
- [Aula 5 do EfficientML.ai - Quantização Parte I](https://www.youtube.com/watch?v=RP23-dRVDWM)
- [Fazendo o Deep Learning Rodar a Todo Vapor a Partir dos Princípios Fundamentais](https://horace.io/brrr_intro.html)
- [Acelerando IA Generativa com PyTorch Parte 2: Otimizações de LLM](https://pytorch.org/blog/accelerating-generative-ai-2/)

