<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Quantization on kenji.blog</title><link>http://kenji.blog/pt/tags/quantization/</link><description>Recent content in Quantization on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>pt</language><copyright>kenjinote</copyright><lastBuildDate>Fri, 11 Sep 2026 00:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/pt/tags/quantization/index.xml" rel="self" type="application/rss+xml"/><item><title>Explicando o mecanismo da tecnologia de quantização (GGUF) do llama.cpp</title><link>http://kenji.blog/pt/p/llama-cpp-quantization-gguf/</link><pubDate>Fri, 11 Sep 2026 00:00:00 +0900</pubDate><guid>http://kenji.blog/pt/p/llama-cpp-quantization-gguf/</guid><description>&lt;img src="http://kenji.blog/p/llama-cpp-quantization-gguf/img/eyecatch.jpg" alt="Featured image of post Explicando o mecanismo da tecnologia de quantização (GGUF) do llama.cpp" />&lt;h2 id="1-introdução-por-que-os-llms-precisam-de-quantização">1. Introdução: Por que os LLMs precisam de quantização?
&lt;/h2>&lt;p>O recente avanço dos grandes modelos de linguagem (LLMs: Large Language Models) tem sido notável, mas nos bastidores surgiram problemas sérios: o &amp;ldquo;esgotamento dos recursos computacionais&amp;rdquo; e o &amp;ldquo;gargalo da largura de banda da memória&amp;rdquo;. Por exemplo, se você carregar um modelo de 70B (70 bilhões) de parâmetros como o Llama 3 na memória usando o padrão de ponto flutuante de 16 bits (FP16), apenas os parâmetros consumirão cerca de 140 GB de VRAM/RAM. Quando o contexto (cache KV) durante a inferência é adicionado a isso, ele não funcionará a menos que se utilize múltiplos clusters de GPUs de ponta para data centers (como NVIDIA A100 80GB ou H100 80GB).&lt;/p>
&lt;p>O &lt;strong>llama.cpp&lt;/strong> e sua tecnologia central de &lt;strong>Quantização (Quantization)&lt;/strong> surgiram como salvadores para permitir que desenvolvedores individuais e dispositivos de borda (MacBooks e PCs gamers comuns) executem LLMs. Em particular, o formato de arquivo chamado &lt;strong>GGUF (GPT-Generated Unified Format)&lt;/strong> e o algoritmo avançado de quantização baseado em blocos chamado &lt;strong>k-quants&lt;/strong> são métodos revolucionários que comprimem o tamanho do modelo para uma fração do seu original, enquanto minimizam a degradação da precisão do modelo (Perplexidade) ao limite absoluto.&lt;/p>
&lt;p>Neste artigo, explicaremos de forma abrangente desde os fundamentos matemáticos da quantização no llama.cpp, as diferenças em relação ao formato GGML, a estrutura detalhada do formato GGUF, até o mecanismo interno do k-quants.&lt;/p>
&lt;hr>
&lt;h2 id="2-fundamentos-matemáticos-da-quantização-quantization">2. Fundamentos Matemáticos da Quantização (Quantization)
&lt;/h2>&lt;p>No contexto de LLMs, a quantização refere-se à operação de mapear valores contínuos (ou números de ponto flutuante de alta precisão) em valores discretos com um número menor de bits (INT8, INT4, INT3, etc.).&lt;/p>
&lt;h3 id="21-fórmulas-básicas-da-quantização-linear">2.1. Fórmulas Básicas da Quantização Linear
&lt;/h3>&lt;p>A abordagem mais simples é a quantização linear (Quantização Min-Max). Suponha que o tensor de pesos original de alta precisão seja $W$, e o tensor de inteiros quantizado seja $W_q$.&lt;/p>
$$ W_q = \text{round}\left( \frac{W}{S} \right) + Z $$
&lt;p>Onde,&lt;/p>
&lt;ul>
&lt;li>$S$ é o &lt;strong>Fator de Escala (Scale Factor)&lt;/strong>, que determina o tamanho do passo (resolução) da quantização.&lt;/li>
&lt;li>$Z$ é o &lt;strong>Ponto Zero (Zero-point)&lt;/strong>, que é um valor de viés para deslocar para qual valor inteiro o número real $0.0$ corresponde após a quantização.&lt;/li>
&lt;li>$\text{round}(\cdot)$ é a função de arredondamento para o número inteiro mais próximo.&lt;/li>
&lt;/ul>
&lt;p>Através da dequantização (Dequantization), um peso real aproximado $\tilde{W}$ é restaurado durante a inferência.&lt;/p>
$$ \tilde{W} = S \times (W_q - Z) $$
&lt;h3 id="22-quantização-simétrica-vs-quantização-assimétrica">2.2. Quantização Simétrica vs Quantização Assimétrica
&lt;/h3>&lt;p>Dependendo do tratamento do ponto zero $Z$, os métodos podem ser amplamente divididos em dois tipos.&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Quantização Assimétrica (Asymmetric Quantization)&lt;/strong>
Mapeia os dados usando o valor mínimo $W_{\min}$ e o valor máximo $W_{\max}$.
&lt;/p>
$$ S = \frac{W_{\max} - W_{\min}}{2^b - 1}, \quad Z = \text{round}\left(-\frac{W_{\min}}{S}\right) $$
&lt;p>
Aqui, $b$ é o número de bits da quantização (ex: para 4 bits, $2^4-1 = 15$). Como é necessário manter $Z$, a sobrecarga de cálculo e memória aumenta ligeiramente.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Quantização Simétrica (Symmetric Quantization)&lt;/strong>
Mapeia ao redor de zero usando o valor máximo absoluto dos dados ($Z=0$).
&lt;/p>
$$ S = \frac{\max(|W_{\max}|, |W_{\min}|)}{2^{b-1} - 1}, \quad Z = 0 $$
&lt;p>
A quantização inicial do llama.cpp (por exemplo, o antigo Q4_0) adotava a quantização simétrica e, por não haver o termo $Z$, tinha a vantagem de tornar o cálculo do produto escalar com instruções SIMD extremamente rápido.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="3-a-evolução-do-ggml-para-o-gguf-e-a-estrutura-de-arquivos">3. A Evolução do GGML para o GGUF e a Estrutura de Arquivos
&lt;/h2>&lt;p>Indispensável ao falar sobre o llama.cpp é o &lt;strong>GGML&lt;/strong>, uma biblioteca de operações de tensores escrita em C++, e o formato de arquivo &lt;strong>GGUF&lt;/strong> derivado dela.&lt;/p>
&lt;h3 id="31-os-desafios-do-ggml">3.1. Os Desafios do GGML
&lt;/h3>&lt;p>As versões iniciais do llama.cpp usavam o formato &lt;code>ggml&lt;/code> (e variantes como &lt;code>ggjt&lt;/code>). No entanto, eles tinham os seguintes problemas:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Falta de extensibilidade:&lt;/strong> Números mágicos e hiperparâmetros eram codificados diretamente com comprimentos e ordens fixas, o que causava alterações que quebravam a compatibilidade sempre que uma nova arquitetura de modelo (ex: Llama, Falcon, Mixtral, etc.) ou um novo tokenizador era adicionado.&lt;/li>
&lt;li>&lt;strong>Perda de compatibilidade com versões anteriores:&lt;/strong> O formato era atualizado com frequência, ocorrendo muitas situações em que arquivos de modelos mais antigos não podiam mais ser carregados nas versões mais recentes do llama.cpp.&lt;/li>
&lt;/ul>
&lt;h3 id="32-o-nascimento-do-formato-gguf">3.2. O Nascimento do Formato GGUF
&lt;/h3>&lt;p>Introduzido em agosto de 2023, o &lt;strong>GGUF&lt;/strong> é um formato altamente versátil projetado para resolver esses problemas. Sua maior característica é a adoção de uma &lt;strong>estrutura de metadados baseada em Chave-Valor (Key-Value)&lt;/strong>.&lt;/p>
&lt;p>O diagrama Mermaid a seguir é uma abstração da estrutura de um arquivo GGUF.&lt;/p>
&lt;div class="mermaid">graph TD
A["Arquivo GGUF"] --> B["Cabeçalho (Magia, Versão)"]
A --> C["Metadados (Pares Chave-Valor)"]
A --> D["Informações do Tensor (Nome, Forma, Deslocamento)"]
A --> E["Dados do Tensor (Carga binária)"]
C --> C1["general.architecture: llama"]
C --> C2["llama.context_length: 4096"]
C --> C3["tokenizer.ggml.tokens: [...]"]
E --> E1["Pesos da Camada 0"]
E --> E2["Pesos da Camada 1"]
E --> E3["..."]&lt;/div>
&lt;p>&lt;strong>Principais vantagens do GGUF:&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Flexibilidade:&lt;/strong> Todos os hiperparâmetros do modelo, configurações de RoPE (Rotary Positional Embedding), dados de vocabulário do tokenizador, etc., são armazenados como pares de Chave-Valor nomeados. Chaves desconhecidas são ignoradas, facilitando a adição de novos recursos.&lt;/li>
&lt;li>&lt;strong>Independência de Endianness:&lt;/strong> O GGUF adota o little-endian por padrão, mas possui um sinalizador (flag) explícito, de modo que é portável de forma segura entre arquiteturas diferentes.&lt;/li>
&lt;li>&lt;strong>Otimização para mmap (mapeamento de memória):&lt;/strong> Os dados do tensor são alinhados (com preenchimento) em limites específicos e podem ser mapeados diretamente do disco para o espaço de memória usando a chamada de sistema &lt;code>mmap()&lt;/code> do SO. Com isso, o tempo de inicialização para carregar o modelo é virtualmente zero.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="4-o-abismo-dos-k-quants-quantização-avançada-baseada-em-blocos">4. O Abismo dos k-quants: Quantização Avançada Baseada em Blocos
&lt;/h2>&lt;p>O verdadeiro valor do formato GGUF é o mecanismo chamado &lt;strong>k-quants (K-quantization)&lt;/strong>, que é responsável por comprimir os pesos do modelo.&lt;/p>
&lt;p>Geralmente, os pesos de uma rede neural têm uma forma próxima de uma distribuição normal quando se observa toda a camada, mas localmente existem valores atípicos (Outliers). Se você quantizar os pesos de toda a camada com um fator de escala uniforme $S$, as informações dos pesos menores serão completamente perdidas, arrastadas pelos valores atípicos.&lt;/p>
&lt;p>Para evitar isso, o llama.cpp realiza a &lt;strong>quantização baseada em blocos (Block-wise Quantization)&lt;/strong>. Os tensores de pesos são divididos em pequenos blocos (por exemplo, 32 elementos ou 256 elementos), e cada bloco tem seu próprio fator de escala (e ponto zero).&lt;/p>
&lt;h3 id="41-limitações-da-quantização-legada-q4_0-q4_1">4.1. Limitações da Quantização Legada (Q4_0, Q4_1)
&lt;/h3>&lt;p>O &lt;code>Q4_0&lt;/code> inicial agrupava 32 pesos FP16 em um bloco e compartilhava um fator de escala FP16.&lt;/p>
&lt;ul>
&lt;li>Tamanho do bloco: 32&lt;/li>
&lt;li>Memória: 1 escala (16 bits) + 32 pesos de 4 bits (128 bits) = 144 bits&lt;/li>
&lt;li>Bits efetivos por elemento (bpw: bits per weight): $144 / 32 = 4.5$ bpw&lt;/li>
&lt;/ul>
&lt;p>Embora isso já seja bastante bom, os limites de precisão e taxa de compressão tornaram-se evidentes. Foi então que surgiram os &lt;strong>k-quants&lt;/strong>, que possuem uma estrutura hierárquica mais complexa e refinada.&lt;/p>
&lt;h3 id="42-estrutura-hierárquica-de-super-blocos-e-sub-blocos-exemplo-do-q4_k_m">4.2. Estrutura Hierárquica de Super-blocos e Sub-blocos (Exemplo do Q4_K_M)
&lt;/h3>&lt;p>O k-quants tem uma estrutura hierárquica com um grande &amp;ldquo;Super-bloco (Super-block)&amp;rdquo; e pequenos &amp;ldquo;Sub-blocos (Sub-blocks)&amp;rdquo; contidos dentro dele. Isso permite quantizar até mesmo os próprios metadados (como os valores de escala), mantendo a precisão enquanto reduz o bpw ao limite extremo.&lt;/p>
&lt;p>Vejamos a estrutura do &lt;strong>Q4_K_M&lt;/strong>, a configuração mais popular. O Q4_K_M usa super-blocos de 256 elementos.&lt;/p>
&lt;div class="mermaid">graph TD
A["Super-bloco (256 pesos)"] --> B["Metadados de escala (FP16/INT8)"]
A --> C["Sub-bloco 0 (32 pesos, 4 bits)"]
A --> D["Sub-bloco 1 (32 pesos, 4 bits)"]
A --> E["..."]
A --> F["Sub-bloco 7 (32 pesos, 4 bits)"]
B --> B1["Super-escala (FP16)"]
B --> B2["Sub-escalas (8 x 6 bits)"]
B --> B3["Sub-mínimos (8 x 6 bits)"]&lt;/div>
&lt;p>A estrutura real em C++ (GGML) é definida conceitualmente da seguinte forma:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Estrutura conceitual de block_q4_K no llama.cpp
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="cp">#define QK_K 256
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">struct&lt;/span> &lt;span class="nc">block_q4_K&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">uint8_t&lt;/span> &lt;span class="n">d&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// Super-escala de todo o super-bloco (ex: FP16 x 2)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">uint8_t&lt;/span> &lt;span class="n">scales&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">12&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// Dados empacotados com a escala de 6 bits e valor mínimo de 6 bits (ponto zero) de 8 sub-blocos (32 elementos cada)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">uint8_t&lt;/span> &lt;span class="n">qs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">QK_K&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// Dados de pesos quantizados em 4 bits (256 elementos / 2 = 128 bytes)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>Processo matemático de dequantização:&lt;/strong>&lt;/p>
&lt;p>O valor real aproximado $\tilde{W}_{i, j}$ do elemento $j$ ($0 \le j &lt; 32$) dentro do sub-bloco $i$ ($0 \le i &lt; 8$) é calculado da seguinte forma:&lt;/p>
$$ \tilde{W}_{i, j} = S_{\text{super}} \times s_i \times (w_{i, j} - m_i) $$
&lt;ul>
&lt;li>$S_{\text{super}}$: Escala de ponto flutuante de todo o super-bloco&lt;/li>
&lt;li>$s_i$: Escala quantizada em 6 bits para o sub-bloco $i$&lt;/li>
&lt;li>$m_i$: Valor mínimo quantizado em 6 bits (ponto zero) para o sub-bloco $i$&lt;/li>
&lt;li>$w_{i, j}$: Peso quantizado de 4 bits ($0 \dots 15$)&lt;/li>
&lt;/ul>
&lt;p>Com essa estrutura hierárquica, mantendo a capacidade de adaptação a valores atípicos, a quantidade de memória ocupada pelos próprios fatores de escala é drasticamente reduzida. O Q4_K_M alcança no geral cerca de &lt;strong>4.8 bpw&lt;/strong>.&lt;/p>
&lt;h3 id="43-diversas-opções-dos-k-quants">4.3. Diversas Opções dos k-quants
&lt;/h3>&lt;p>O llama.cpp oferece muitas variações dependendo do propósito. O sufixo após o &amp;ldquo;K&amp;rdquo; (S, M, L) indica o tamanho.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:left">Formato&lt;/th>
&lt;th style="text-align:center">BPW (Bits por Peso)&lt;/th>
&lt;th style="text-align:left">Visão Geral e Características&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q2_K&lt;/strong>&lt;/td>
&lt;td style="text-align:center">2.5～3.3&lt;/td>
&lt;td style="text-align:left">Compressão extrema. Queda significativa na precisão, mas ideal para ambientes com VRAM extremamente baixa.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q3_K_M&lt;/strong>&lt;/td>
&lt;td style="text-align:center">3.3&lt;/td>
&lt;td style="text-align:left">Padrão para quantização de 3 bits. Piora em relação ao Q4, mas frequentemente fica dentro de limites aceitáveis.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q4_K_M&lt;/strong>&lt;/td>
&lt;td style="text-align:center">4.8&lt;/td>
&lt;td style="text-align:left">&lt;strong>Ponto ideal recomendado&lt;/strong>. Equilibra a redução pela metade do tamanho do modelo com a manutenção da precisão.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q5_K_M&lt;/strong>&lt;/td>
&lt;td style="text-align:center">5.5&lt;/td>
&lt;td style="text-align:left">Quando é necessária maior precisão. Posição intermediária entre o Q4 e o FP16.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q6_K&lt;/strong>&lt;/td>
&lt;td style="text-align:center">6.6&lt;/td>
&lt;td style="text-align:left">Mantém uma Perplexidade quase equivalente à do FP16, mas com um tamanho de arquivo maior.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Q8_0&lt;/strong>&lt;/td>
&lt;td style="text-align:center">8.5&lt;/td>
&lt;td style="text-align:left">Equivalente ao INT8. Usado principalmente para tensores intermediários em cálculos durante a inferência ou apenas nas camadas finais.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;em>Nota: O BPW real é nivelado (média) em todo o modelo através de uma Quantização Mista (Mixed Quantization) dependendo do tipo do tensor no modelo (por exemplo, se é uma projeção Q/K/V da Attention ou os pesos da FFN). Otimizações são feitas internamente, como quantizar tensores importantes em Q6 e o resto em Q4.&lt;/em>&lt;/p>
&lt;hr>
&lt;h2 id="5-otimização-do-desempenho-de-inferência-simd-e-arquitetura-cuda">5. Otimização do Desempenho de Inferência: SIMD e Arquitetura CUDA
&lt;/h2>&lt;p>Apenas carregar um modelo GGUF na memória não torna a inferência rápida. A maior parte da inferência de LLMs é a &amp;ldquo;Multiplicação de Matrizes (Matrix-Vector Multiplication, abreviado como GEMV, ou Matrix-Matrix, GEMM)&amp;rdquo;. A chave é como acelerar a operação de produto e soma dos pesos quantizados com as ativações (dados de entrada) mantidas em FP16 (ou FP32).&lt;/p>
&lt;h3 id="51-utilização-de-instruções-simd-em-ambiente-cpu">5.1. Utilização de Instruções SIMD em Ambiente CPU
&lt;/h3>&lt;p>A razão pela qual o llama.cpp possui uma velocidade incrível na inferência por CPU se deve à otimização &lt;strong>SIMD (Single Instruction, Multiple Data)&lt;/strong> em nível de assembly.
Por exemplo, as CPUs Intel/AMD utilizam totalmente os conjuntos de instruções &lt;strong>AVX2&lt;/strong> ou &lt;strong>AVX-512&lt;/strong>, enquanto o Apple Silicon utiliza as instruções &lt;strong>ARM NEON&lt;/strong>.&lt;/p>
&lt;p>Durante a inferência, $W_q$ não é intencionalmente convertido de volta para FP32 (dequantizado) antes de fazer a multiplicação.
O lado das ativações também é quantizado dinamicamente bloco a bloco (Dynamic Quantization, geralmente para INT8), e a operação com inteiros de &lt;strong>INT8 $\times$ INT4&lt;/strong> é calculada de uma vez usando instruções SIMD especiais de produto escalar (ex: &lt;code>vdpaddd&lt;/code> ou &lt;code>_mm256_madd_epi16&lt;/code>). Ao converter de volta para FP32 no acumulador final e multiplicar pelo fator de escala, alcança-se um throughput incrível.&lt;/p>
&lt;h3 id="52-descarregamento-offload-em-ambiente-gpu-cublas--cuda">5.2. Descarregamento (Offload) em Ambiente GPU (cuBLAS / CUDA)
&lt;/h3>&lt;p>Recentemente, o llama.cpp não possui apenas suporte para CPU, mas também um suporte robusto para GPUs NVIDIA (CUBLAS / CUDA).
É possível descarregar parte ou a totalidade das camadas do arquivo GGUF para a VRAM (através da opção &lt;code>--n-gpu-layers&lt;/code>).&lt;/p>
&lt;div class="mermaid">sequenceDiagram
participant User as Usuário
participant CPU_RAM as CPU &amp; RAM (mmap)
participant VRAM as VRAM da GPU
participant Compute as Tensor Cores
User->>CPU_RAM: Carregar GGUF (mmap)
CPU_RAM->>VRAM: Descarregar Camadas (ex: 30/32 camadas)
Note over CPU_RAM, VRAM: Os dados permanecem quantizados na VRAM
User->>Compute: Forward Pass (Tokens de Entrada)
Compute->>VRAM: Buscar Pesos Quantizados
Compute->>Compute: Dequantizar on-the-fly para FP16 na SRAM
Compute->>Compute: Multiplicação de Matrizes (cuBLAS / Kernels Personalizados)
Compute->>User: Saída (Logits)&lt;/div>
&lt;p>Ao processar na GPU, a largura de banda de memória (Memory Bandwidth) da VRAM se torna o maior gargalo. Como os pesos estão comprimidos com k-quants, a quantidade de dados transferida da VRAM para as unidades de computação da GPU (SM: Streaming Multiprocessor ou Tensor Cores) é reduzida a cerca de 1/3 ~ 1/4. No exato momento em que os pesos chegam à unidade de cálculo, eles são dequantizados (descompactados) on-the-fly para FP16, e a multiplicação de matrizes é executada em altíssima velocidade usando o Tensor Core.
Em outras palavras, pode-se dizer que a quantização não é feita para &amp;ldquo;reduzir a quantidade de cálculos&amp;rdquo;, mas sim para &lt;strong>&amp;ldquo;reduzir a quantidade de transferências de memória&amp;rdquo;&lt;/strong>.&lt;/p>
&lt;hr>
&lt;h2 id="6-exemplo-concreto-do-trade-off-entre-uso-de-memória-e-desempenho">6. Exemplo Concreto do Trade-off entre Uso de Memória e Desempenho
&lt;/h2>&lt;p>Abaixo, usando o modelo Llama 3 8B como exemplo, vejamos as especificações exigidas por nível de quantização do GGUF. (Os valores são apenas estimativas aproximadas)&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:left">Modelo/Quantização&lt;/th>
&lt;th style="text-align:left">Tamanho do Arquivo&lt;/th>
&lt;th style="text-align:left">VRAM/RAM Necessária&lt;/th>
&lt;th style="text-align:left">Velocidade de Inferência (Estimativa)&lt;/th>
&lt;th style="text-align:left">Degradação da Perplexidade&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (FP16)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">Aprox. 16 GB&lt;/td>
&lt;td style="text-align:left">18 GB ou mais&lt;/td>
&lt;td style="text-align:left">Base&lt;/td>
&lt;td style="text-align:left">Nenhuma (Base)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (Q8_0)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">Aprox. 8.5 GB&lt;/td>
&lt;td style="text-align:left">10 GB ou mais&lt;/td>
&lt;td style="text-align:left">Rápida&lt;/td>
&lt;td style="text-align:left">Quase zero&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (Q6_K)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">Aprox. 6.6 GB&lt;/td>
&lt;td style="text-align:left">8 GB ou mais&lt;/td>
&lt;td style="text-align:left">Muito rápida&lt;/td>
&lt;td style="text-align:left">Mínima&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (Q4_K_M)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">Aprox. 4.9 GB&lt;/td>
&lt;td style="text-align:left">6.5 GB ou mais&lt;/td>
&lt;td style="text-align:left">Mais rápida e ideal&lt;/td>
&lt;td style="text-align:left">Aceitável e pequena&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (Q3_K_M)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">Aprox. 3.9 GB&lt;/td>
&lt;td style="text-align:left">5.5 GB ou mais&lt;/td>
&lt;td style="text-align:left">Mais rápida&lt;/td>
&lt;td style="text-align:left">Um pouco perceptível&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Llama-3-8B (Q2_K)&lt;/strong>&lt;/td>
&lt;td style="text-align:left">Aprox. 3.0 GB&lt;/td>
&lt;td style="text-align:left">4.5 GB ou mais&lt;/td>
&lt;td style="text-align:left">Rápida&lt;/td>
&lt;td style="text-align:left">Degradação óbvia&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Atenção (O impacto do cache KV):&lt;/strong>
Na inferência de LLMs, conforme o tamanho do contexto (número de tokens no prompt) aumenta, o consumo de memória do &lt;strong>cache KV&lt;/strong>, que guarda os estados passados da Attention além dos pesos do modelo, aumenta explosivamente.
Por exemplo, com um contexto de 8192 tokens, apenas o cache KV já consome vários GBs. Portanto, para o uso real, é necessário garantir uma margem (Headroom) de &lt;code>tamanho do arquivo do modelo + aprox. 1.5GB a 3GB&lt;/code>. O motivo pelo qual o Q4_K_M é recomendado é que ele atinge a medida exata para rodar com segurança numa placa de vídeo comum com 8GB de VRAM (como RTX 3060 / 4060) mesmo com essa alocação de cache KV.&lt;/p>
&lt;p>Em versões recentes do llama.cpp, foi adicionada a funcionalidade para &lt;strong>quantizar o próprio cache KV em Q8_0 ou Q4_0&lt;/strong>, de modo que há um esforço constante para possibilitar contextos ainda mais longos.&lt;/p>
&lt;hr>
&lt;h2 id="7-conclusão">7. Conclusão
&lt;/h2>&lt;p>Neste artigo, exploramos profundamente a estrutura interna do formato GGUF e da tecnologia de quantização k-quants, que formam o coração do llama.cpp.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Flexibilidade do GGUF:&lt;/strong> O uso de uma estrutura de metadados do tipo chave-valor construiu um ecossistema robusto, capaz de acompanhar a rápida evolução dos LLMs (surgimento de novas arquiteturas) sem causar quebras de compatibilidade.&lt;/li>
&lt;li>&lt;strong>Compressão Extrema via k-quants:&lt;/strong> O gerenciamento hierárquico dos fatores de escala através de super-blocos e sub-blocos permitiu uma incrível taxa de compressão, alcançando em média 4.8 bits por peso (Q4_K_M), mantendo as informações de valores atípicos.&lt;/li>
&lt;li>&lt;strong>Resolução do Gargalo da Largura de Banda de Memória:&lt;/strong> Implementações avançadas de kernels no SIMD e em CUDA permitiram a realização dos cálculos combinados com a dequantização on-the-fly, o que reduziu drasticamente o tráfego da VRAM e impulsionou imensamente a velocidade de inferência.&lt;/li>
&lt;/ol>
&lt;p>Pode-se dizer sem exagero que a destreza técnica do llama.cpp em promover a democratização da IA vai além da simples criação de uma ferramenta, situando-se em um dos mais altos níveis da engenharia de software contemporânea. Ao compreender a mecânica dos algoritmos de quantização e o formato GGUF, você poderá selecionar o modelo mais adequado e ajustar com precisão a performance de acordo com o seu ambiente.&lt;/p>
&lt;h3 id="links-de-referência">Links de Referência
&lt;/h3>&lt;ul>
&lt;li>&lt;a class="link" href="https://github.com/ggerganov/llama.cpp" target="_blank" rel="noopener"
>Repositório GitHub do llama.cpp&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://github.com/ggerganov/ggml/blob/master/docs/gguf.md" target="_blank" rel="noopener"
>Especificação do Formato GGUF&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://github.com/ggerganov/llama.cpp/pull/1684" target="_blank" rel="noopener"
>PR de Implementação dos K-quants&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>(Fim)&lt;/p></description></item></channel></rss>