<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Optimization on kenji.blog</title><link>http://kenji.blog/pt/categories/optimization/</link><description>Recent content in Optimization on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>pt</language><copyright>kenjinote</copyright><lastBuildDate>Fri, 11 Sep 2026 01:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/pt/categories/optimization/index.xml" rel="self" type="application/rss+xml"/><item><title>Técnicas de Resolução para Falta de Memória de GPU no Desenvolvimento de IA (CPU Offloading, etc.)</title><link>http://kenji.blog/pt/p/ai-gpu-vram-optimization-cpu-offloading/</link><pubDate>Fri, 11 Sep 2026 01:00:00 +0900</pubDate><guid>http://kenji.blog/pt/p/ai-gpu-vram-optimization-cpu-offloading/</guid><description>&lt;img src="http://kenji.blog/p/ai-gpu-vram-optimization-cpu-offloading/img/eyecatch.jpg" alt="Featured image of post Técnicas de Resolução para Falta de Memória de GPU no Desenvolvimento de IA (CPU Offloading, etc.)" />&lt;h1 id="introdução-desenvolvimento-de-ia-e-a-barreira-da-vram">Introdução: Desenvolvimento de IA e a &amp;ldquo;Barreira da VRAM&amp;rdquo;
&lt;/h1>&lt;p>Nos últimos anos, tecnologias de IA generativa, como Modelos de Linguagem de Grande Escala (LLM) e Modelos de Difusão (Diffusion Models), têm alcançado um rápido desenvolvimento. No entanto, ao treinar (fine-tuning) ou executar inferência (Inference) desses modelos de IA de ponta em ambientes locais, muitos desenvolvedores e pesquisadores enfrentam uma barreira extremamente física: a &lt;strong>&amp;ldquo;falta de memória da GPU (VRAM)&amp;rdquo;&lt;/strong>.&lt;/p>
&lt;p>Mesmo em GPUs de ponta para consumidores, como a NVIDIA GeForce RTX 4090, a VRAM é de no máximo 24 GB, o que torna completamente impossível carregar um modelo gigante como o Llama 3 70B em sua forma original. GPUs voltadas para data centers, como H100 (80GB) e B200 (192GB), são extremamente caras e não são algo que indivíduos ou pequenas equipes possam acessar facilmente. Se não conseguirmos superar essa &amp;ldquo;Barreira da VRAM (The Wall of VRAM)&amp;rdquo;, não poderemos sequer tocar nos modelos mais avançados.&lt;/p>
&lt;p>Neste artigo, explicaremos detalhadamente técnicas avançadas para quebrar essa restrição física de limite de VRAM por meio de inovações na arquitetura de software e hardware, tanto na perspectiva da inferência quanto do treinamento. Exploraremos a fundo, com fórmulas matemáticas e diagramas, o CPU offloading, otimização de cache KV, gradient checkpointing e a mais recente arquitetura de Memória Unificada (Unified Memory). Ao ler este artigo, você entenderá profundamente o comportamento da VRAM e adquirirá conhecimentos práticos para lidar com modelos gigantescos usando recursos limitados.&lt;/p>
&lt;hr>
&lt;h1 id="1-anatomia-do-consumo-de-vram-de-modelos-de-ia-inferência-e-treinamento">1. Anatomia do Consumo de VRAM de Modelos de IA (Inferência e Treinamento)
&lt;/h1>&lt;p>O primeiro passo para resolver a falta de VRAM é entender com precisão, de uma perspectiva micro, &amp;ldquo;o que&amp;rdquo; está consumindo &amp;ldquo;quanta&amp;rdquo; memória. Se pudermos estimar o consumo com precisão usando fórmulas matemáticas, em vez de tratá-lo como uma caixa preta, poderemos escolher a técnica de otimização apropriada.&lt;/p>
&lt;h2 id="11-cálculo-de-memória-dos-parâmetros-do-modelo-pesos">1.1 Cálculo de Memória dos Parâmetros do Modelo (Pesos)
&lt;/h2>&lt;p>A quantidade básica de memória consumida pelos parâmetros (Weights) que compõem um modelo de IA é determinada pelo número total de parâmetros do modelo e pelo tipo de dados (Precision: precisão) usado para representá-los.&lt;/p>
&lt;p>Os tipos de dados comumente usados em deep learning e o número de bytes por parâmetro ($B$) são os seguintes:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>FP32 (Ponto flutuante de precisão simples):&lt;/strong> 4 bytes (precisão padrão durante o treinamento)&lt;/li>
&lt;li>&lt;strong>FP16 / BF16 (Ponto flutuante de meia precisão):&lt;/strong> 2 bytes (inferência geral e treinamento de precisão mista)&lt;/li>
&lt;li>&lt;strong>INT8 (Inteiro de 8 bits):&lt;/strong> 1 byte (modelos quantizados)&lt;/li>
&lt;li>&lt;strong>INT4 (Quantização de inteiro de 4 bits):&lt;/strong> 0.5 bytes (quantização extrema como GPTQ, AWQ, GGUF)&lt;/li>
&lt;/ul>
&lt;p>Considerando o número total de parâmetros do modelo inteiro como $P$, a quantidade de memória base $M_{weights}$ ocupada pelos próprios pesos é expressa pela seguinte fórmula:&lt;/p>
$$ M_{weights} = P \times B $$&lt;p>Por exemplo, se carregarmos o modelo &amp;ldquo;Llama 3 8B&amp;rdquo; (aproximadamente 8 bilhões de parâmetros) publicado pela Meta em FP16 (meia precisão), o cálculo seria o seguinte:&lt;/p>
$$ M_{weights} = 8,000,000,000 \times 2 \text{ bytes} \approx 16,000,000,000 \text{ bytes} \approx 16 \text{ GB} $$&lt;p>Em outras palavras, puramente carregar os pesos do modelo na GPU consumirá 16 GB de VRAM. Em uma RTX 3060 (12 GB), ocorreria um erro de Out of Memory (OOM) neste momento. No entanto, se quantizarmos o modelo para INT4, passará a ser $8 \times 0.5 = 4 \text{ GB}$, o que pode ser carregado com folga.&lt;/p>
&lt;h2 id="12-consumo-de-memória-durante-a-inferência-aumento-do-cache-kv">1.2 Consumo de Memória Durante a Inferência: Aumento do Cache KV
&lt;/h2>&lt;p>Durante a inferência de LLMs (especialmente na geração de texto autorregressiva), o &lt;strong>Cache KV (Key-Value Cache)&lt;/strong> pressiona a VRAM tão ou mais intensamente do que os próprios pesos.
Na arquitetura Transformer, para evitar o recálculo de informações de tokens gerados ou processados no passado, os tensores de Key e Value em cada camada de atenção são mantidos em cache na VRAM. Isso melhora a velocidade de cálculo (Compute), mas o consumo de memória aumenta linearmente e de forma explosiva à medida que o comprimento do contexto (comprimento do prompt de entrada + comprimento do texto gerado) aumenta.&lt;/p>
&lt;p>A quantidade de memória de cache KV $M_{kv\_token}$ consumida ao processar 1 token é rigorosamente calculada pela seguinte fórmula com base na arquitetura do modelo:&lt;/p>
$$ M_{kv\_token} = 2 \times N_{layers} \times N_{heads\_kv} \times D_{head} \times B $$&lt;p>Aqui, cada variável tem o seguinte significado:&lt;/p>
&lt;ul>
&lt;li>$2$ : Porque existem dois tensores, Key e Value&lt;/li>
&lt;li>$N_{layers}$ : Número de camadas (layers) do Transformer&lt;/li>
&lt;li>$N_{heads\_kv}$ : Número de cabeças de atenção KV (no caso de GQA: Grouped Query Attention, será menor do que o número normal de cabeças)&lt;/li>
&lt;li>$D_{head}$ : Dimensionalidade de cada cabeça (geralmente, dimensão da camada oculta $D_{model} / N_{heads}$)&lt;/li>
&lt;li>$B$ : Número de bytes do tipo de dado (2 se FP16)&lt;/li>
&lt;/ul>
&lt;p>A quantidade total de cache KV $M_{kv\_total}$ será este valor multiplicado pelo comprimento da sequência ($L_{seq}$) e pelo tamanho do lote ($BatchSize$).&lt;/p>
$$ M_{kv\_total} = M_{kv\_token} \times L_{seq} \times BatchSize $$&lt;p>&lt;strong>Exemplo Específico: No caso do Llama 2 7B&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>$N_{layers} = 32$&lt;/li>
&lt;li>$N_{heads\_kv} = 32$ (No caso de MHA)&lt;/li>
&lt;li>$D_{head} = 128$&lt;/li>
&lt;li>FP16 ($B=2$)&lt;/li>
&lt;li>Tamanho do lote 1, comprimento da sequência 8192 (Contexto 8K)&lt;/li>
&lt;/ul>
$$ M_{kv\_total} = 2 \times 32 \times 32 \times 128 \times 2 \times 8192 \times 1 = 4,294,967,296 \text{ bytes} \approx 4 \text{ GB} $$&lt;p>Se estendermos o contexto para 32K (32768 tokens), o cache KV consumirá cerca de 16 GB sozinho. Se aumentarmos o tamanho do lote para 4, será de 64 GB. Exigir uma VRAM muito maior do que o tamanho do próprio modelo é um grande desafio durante a inferência.&lt;/p>
&lt;h2 id="13-consumo-de-memória-durante-o-treinamento-otimizadores-gradientes-e-ativações">1.3 Consumo de Memória Durante o Treinamento: Otimizadores, Gradientes e Ativações
&lt;/h2>&lt;p>Em comparação com a inferência, o treinamento de modelos (pré-treinamento e fine-tuning) consome significativamente mais VRAM. Isso ocorre porque, em vez de apenas um passe frontal simples (forward pass), precisamos reter as informações necessárias para a retropropagação (backpropagation). A memória de treinamento consiste principalmente nos seguintes 4 elementos:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Pesos do Modelo (Model Weights):&lt;/strong> Semelhante à inferência, mas no treinamento de precisão mista (mixed precision), os pesos de FP16 e FP32 (pesos mestre) muitas vezes são ambos retidos.&lt;/li>
&lt;li>&lt;strong>Gradientes (Gradients):&lt;/strong> Gradientes para cada parâmetro calculados via retropropagação. 2 bytes por parâmetro no caso de FP16.&lt;/li>
&lt;li>&lt;strong>Estados do Otimizador (Optimizer States):&lt;/strong> Otimizadores avançados como AdamW retêm o primeiro momento (Momentum) e o segundo momento (Variance) para cada parâmetro. Para manter a estabilidade do treinamento, estes são geralmente mantidos em FP32 (4 bytes). Isso significa que consumimos $4 + 4 = 8$ bytes por parâmetro em dois momentos.&lt;/li>
&lt;li>&lt;strong>Ativações (Activations):&lt;/strong> Para o cálculo de gradientes na retropropagação, a saída de cada camada (estado intermediário) durante o passe frontal precisa ser mantida na memória. Isso depende fortemente do tamanho do lote e do comprimento da sequência e torna-se extremamente grande.&lt;/li>
&lt;/ol>
&lt;p>Em resumo, no treinamento de precisão mista (Mixed Precision Training) usando o otimizador Adam padrão, são necessários &lt;strong>cerca de 16 a 20 bytes&lt;/strong> por parâmetro (peso mestre 4 + peso FP16 2 + gradiente 2 + otimizador 8 + α) de memória.&lt;/p>
$$ M_{train\_param} \approx P \times 16 \text{ bytes} $$&lt;p>Para o treinamento de um modelo de 7B (7 bilhões de parâmetros), calcula-se que requer $7B \times 16 = 112 \text{ GB}$ apenas para dados relacionados aos parâmetros, e com a adição das ativações, requer impressionantes 140 GB ou mais de VRAM. Para executar isso com 24 GB de VRAM, técnicas de otimização extremas explicadas nos capítulos seguintes são indispensáveis.&lt;/p>
&lt;hr>
&lt;h1 id="2-técnicas-de-economia-de-vram-durante-a-inferência">2. Técnicas de Economia de VRAM Durante a Inferência
&lt;/h1>&lt;p>Muitas tecnologias de software foram desenvolvidas como abordagens para executar modelos gigantescos, superando os limites de hardware durante a inferência.&lt;/p>
&lt;h2 id="21-cpu-offloading-descarregamento-na-cpu-e-divisão-de-camadas">2.1 CPU Offloading (Descarregamento na CPU) e Divisão de Camadas
&lt;/h2>&lt;p>Quando um modelo gigante não cabe em uma ou mais GPUs, o método de alocar parte do modelo na memória do sistema (CPU RAM) e transferi-la para a GPU apenas quando necessário é chamado de &lt;strong>CPU offloading&lt;/strong>. Bibliotecas como &lt;code>llama.cpp&lt;/code> e &lt;code>Accelerate&lt;/code> do Hugging Face suportam essa funcionalidade.&lt;/p>
&lt;pre class="mermaid">
graph TD
A[&amp;#34;RAM do Sistema (DDR4 / DDR5)&amp;#34;] --&amp;gt; B[&amp;#34;VRAM da GPU (GDDR6X)&amp;#34;]
B[&amp;#34;VRAM da GPU (GDDR6X)&amp;#34;] --&amp;gt; C[&amp;#34;Tensor Cores (Cálculo)&amp;#34;]
subgraph &amp;#34;Divisão de Camadas e Offloading&amp;#34;
D[&amp;#34;Camadas Inferiores 1-15 (Fixadas na GPU)&amp;#34;]
E[&amp;#34;Camadas Superiores 16-32 (Descarregadas na CPU)&amp;#34;]
end
E[&amp;#34;Camadas Superiores 16-32 (Descarregadas na CPU)&amp;#34;] -.-&amp;gt; B[&amp;#34;VRAM da GPU (GDDR6X)&amp;#34;]
&lt;/pre>
&lt;p>&lt;strong>Mecanismos e Desafios:&lt;/strong>
Como o modelo Transformer tem uma estrutura na qual as camadas são empilhadas em série, o cálculo da próxima camada não começará até que o cálculo de uma certa camada termine. Aproveitando isso, mantemos residentes (fixadas) na VRAM apenas as camadas que cabem na GPU (por exemplo, camadas de 1 a 15) e colocamos as camadas restantes (camadas de 16 a 32) na CPU RAM, que tem uma grande capacidade, mas baixa velocidade. Durante a inferência, quando o cálculo até a 15ª camada terminar, os pesos da 16ª camada são transferidos (copiados) da CPU para a GPU pelo barramento PCIe, e o cálculo é executado na GPU.&lt;/p>
&lt;p>No entanto, &lt;strong>a largura de banda (Bandwidth) do PCIe se torna um gargalo severo&lt;/strong>. A largura de banda máxima teórica do PCIe 4.0 x16 é de 32 GB/s (unidirecional), o que é duas ordens de grandeza mais lento em comparação à largura de banda interna de VRAM das GPUs mais recentes (por exemplo, a GDDR6X da RTX 4090 atinge 1008 GB/s, e a HBM3 da H100, mais de 3 TB/s). Assim, se usarmos o CPU offloading excessivamente, a velocidade de inferência (Tokens per Second) cairá drasticamente.
Para minimizar a degradação da velocidade, o ponto-chave prático é colocar o maior número possível de camadas na GPU (maximizar as GPU Layers) e minimizar as camadas descarregadas (offloaded).&lt;/p>
&lt;h2 id="22-quantização-de-cache-kv-e-pagedattention">2.2 Quantização de Cache KV e PagedAttention
&lt;/h2>&lt;p>Existem duas otimizações poderosas para o cache KV, que é a principal causa do consumo de VRAM durante a inferência.&lt;/p>
&lt;p>&lt;strong>1. Quantização de Cache KV (KV Cache Quantization):&lt;/strong>
É uma técnica que quantiza dinamicamente não apenas os pesos do modelo, mas também o próprio cache KV gerado em tempo de execução, armazenando-o na VRAM em INT8, INT4 ou até FP8. Com isso, o tamanho do cache KV pode ser reduzido de 50% a 75%. Essa função é incorporada em motores de inferência modernos (vLLM e llama.cpp), proporcionando economias substanciais de VRAM enquanto minimiza a degradação da precisão.&lt;/p>
&lt;p>&lt;strong>2. PagedAttention:&lt;/strong>
&lt;strong>PagedAttention&lt;/strong>, introduzido pelo motor de inferência vLLM, aplica o conceito de &amp;ldquo;paginação&amp;rdquo; de memória virtual do SO ao cache KV. Nos motores de inferência tradicionais, uma área contígua de VRAM era previamente alocada (Pre-allocation) com base no comprimento máximo definido da sequência. Por isso, quando a entrada real era curta, ocorria fragmentação e desperdício de memória não utilizada, onde às vezes mais de 60% da VRAM era desperdiçada.&lt;/p>
&lt;p>O PagedAttention permite que o cache KV seja dividido em blocos de tamanho fixo (páginas) e armazenado de forma distribuída em espaços de memória física não contíguos. Com isso, os desperdícios de memória são reduzidos a quase zero (restritos apenas à fragmentação interna), possibilitando um aumento expressivo no tamanho do lote com a mesma capacidade de VRAM.&lt;/p>
&lt;pre class="mermaid">
graph LR
A[&amp;#34;Cache KV Lógico&amp;#34;] --&amp;gt; B[&amp;#34;Blocos Físicos de VRAM&amp;#34;]
A1[&amp;#34;Token 1, 2, 3, 4&amp;#34;] --&amp;gt; B3[&amp;#34;Bloco 3 (Alocado)&amp;#34;]
A2[&amp;#34;Token 5, 6, 7, 8&amp;#34;] --&amp;gt; B1[&amp;#34;Bloco 1 (Alocado)&amp;#34;]
A3[&amp;#34;Tokens Futuros...&amp;#34;] -.-&amp;gt; B2[&amp;#34;Bloco 2 (Livre)&amp;#34;]
&lt;/pre>
&lt;h2 id="23-flashattention-rompendo-a-complexidade-de-memória-do-cálculo-de-atenção">2.3 FlashAttention: Rompendo a Complexidade de Memória do Cálculo de Atenção
&lt;/h2>&lt;p>A falta de VRAM não é causada apenas pela quantidade de memória que armazena os dados, mas também pela falta de &amp;ldquo;espaço de trabalho (workspace) temporário&amp;rdquo; durante o cálculo. O mecanismo Self-Attention padrão do Transformer precisa materializar (Materialize) uma gigantesca matriz de atenção de $N \times N$ na VRAM para um comprimento de sequência $N$. Isso resulta numa complexidade de memória de $O(N^2)$, tornando-se o principal motivo de erros OOM em longos contextos.&lt;/p>
&lt;p>Isso foi resolvido com o &lt;strong>FlashAttention&lt;/strong> (e FlashAttention-2, 3).
O FlashAttention é um algoritmo projetado com base na arquitetura de hardware das GPUs (uma estrutura hierárquica entre uma enorme, mas lenta HBM e uma minúscula, mas ultrarrápida SRAM). Ao usar uma técnica chamada particionamento em blocos (Tiling), ele carrega os dados em blocos para a SRAM e conclui ali os cálculos da atenção, evitando completamente o processo de gravar a matriz $N \times N$ para a HBM (VRAM).&lt;/p>
&lt;p>Como resultado, a complexidade de memória das camadas de atenção cai drasticamente de $O(N^2)$ para $O(N)$ (proporcional ao comprimento da sequência), aliviando significativamente as restrições sobre os comprimentos de contexto.&lt;/p>
&lt;h2 id="24-a-ascensão-da-memória-unificada-unified-memory-e-o-apple-silicon">2.4 A Ascensão da Memória Unificada (Unified Memory) e o Apple Silicon
&lt;/h2>&lt;p>A &lt;strong>Arquitetura de Memória Unificada (Unified Memory Architecture: UMA)&lt;/strong> adotada pelo Apple Silicon (séries M1/M2/M3/M4 Max e Ultra) e por algumas APUs recentes (como a série AMD Strix Point) aborda esse problema fundamentalmente no nível da arquitetura de PC.&lt;/p>
&lt;p>Nessas arquiteturas, a CPU e a GPU na placa-mãe compartilham exatamente a mesma memória física (por exemplo, até 192 GB de LPDDR5). Portanto, o próprio conceito de &amp;ldquo;transferência lenta de dados da CPU para a GPU via PCIe&amp;rdquo; não existe fisicamente.&lt;/p>
&lt;pre class="mermaid">
graph TD
subgraph &amp;#34;Arquitetura de Memória Unificada (ex: Apple Silicon)&amp;#34;
A[&amp;#34;Núcleos da CPU&amp;#34;] &amp;lt;--&amp;gt; C[&amp;#34;Controlador de Memória Compartilhada&amp;#34;]
B[&amp;#34;Núcleos da GPU / Neural Engine&amp;#34;] &amp;lt;--&amp;gt; C[&amp;#34;Controlador de Memória Compartilhada&amp;#34;]
C[&amp;#34;Controlador de Memória Compartilhada&amp;#34;] &amp;lt;--&amp;gt; D[&amp;#34;Pool de Memória Unificada (ex: 192GB)&amp;#34;]
end
&lt;/pre>
&lt;p>A maior vantagem dessa arquitetura é que não há uma parede distinta chamada VRAM, o que permite usar quase toda a memória do sistema para carregar LLMs gigantes diretamente. Um Mac Studio com 192 GB de memória unificada permite carregar e fazer inferência rápida com modelos imensos da classe 70B e superiores (como Grok-1) num único dispositivo, sem quantização. A largura de banda de acesso à memória também chega a 800 GB/s no M2 Ultra, exibindo velocidades comparáveis a GPUs dedicadas de última geração. É uma abordagem extremamente poderosa que resolve o dilema de &amp;ldquo;capacidade de memória&amp;rdquo; vs &amp;ldquo;largura de banda&amp;rdquo; no nível do hardware.&lt;/p>
&lt;hr>
&lt;h1 id="3-técnicas-de-economia-de-vram-durante-o-treinamento-fine-tuning">3. Técnicas de Economia de VRAM Durante o Treinamento (Fine-Tuning)
&lt;/h1>&lt;p>Mesmo durante a fase de treinamento (Training), que requer mais VRAM do que a inferência, ocorreram grandes inovações. Para realizar fine-tuning com recursos limitados, é indispensável a combinação das seguintes tecnologias.&lt;/p>
&lt;h2 id="31-gradient-checkpointing">3.1 Gradient Checkpointing
&lt;/h2>&lt;p>No processo de retropropagação (backpropagation) do aprendizado profundo, para calcular os gradientes, é necessário manter os resultados intermediários (Activations) de todas as camadas obtidas durante o passe frontal (forward pass) na memória. À medida que o comprimento da sequência ou o tamanho do lote aumenta, a memória de ativação começa a dominar a VRAM.&lt;/p>
&lt;p>O &lt;strong>Gradient Checkpointing (também chamado Activation Recomputation)&lt;/strong> é uma técnica brilhante que se aproveita do trade-off entre capacidade de memória e tempo computacional (Compute).
Em vez de salvar todos os resultados intermediários na memória, são mantidos apenas as saídas de camadas específicas (pontos de verificação). Durante a retropropagação, quando valores intermediários não armazenados são necessários, &lt;strong>eles são restaurados recalculando o passe frontal a partir do ponto de verificação mais próximo salvo.&lt;/strong>&lt;/p>
&lt;p>Embora o volume de cálculos aumente em cerca de 20 a 30% e o tempo total de treinamento seja prolongado, o consumo de VRAM decorrente das ativações é drasticamente reduzido de $O(N)$ ($N$ é o número de camadas) para $O(\sqrt{N})$. Nos treinamentos atuais de grandes modelos, pode-se dizer que esse é um recurso tão obrigatório que o processo sequer começa sem ele.&lt;/p>
&lt;h2 id="32-lora-e-qlora-low-rank-adaptation">3.2 LoRA e QLoRA (Low-Rank Adaptation)
&lt;/h2>&lt;p>O protagonista que resolveu de forma fundamental a escassez de VRAM e representa as técnicas de PEFT (Parameter-Efficient Fine-Tuning) é o &lt;strong>LoRA&lt;/strong>.&lt;/p>
&lt;p>A enorme matriz de pesos original do modelo, $W_0 \in \mathbb{R}^{d \times k}$, é congelada (Frozen) e não é treinada. Em vez disso, introduzem-se paralelamente duas matrizes extremamente pequenas de posto baixo (low-rank matrices) $A \in \mathbb{R}^{r \times k}$ e $B \in \mathbb{R}^{d \times r}$, e somente $A$ e $B$ são treinadas (onde o posto $r$ é um valor pequeno $r \ll d, k$).&lt;/p>
$$ W_{adapted} = W_0 + \Delta W = W_0 + B A $$&lt;p>Isso faz com que os parâmetros-alvo de treinamento se tornem menos de 1% do total original (às vezes menos de 0,1%) e, como resultado, consumidores ávidos de memória como &amp;ldquo;gradientes&amp;rdquo; e &amp;ldquo;estados de otimizadores&amp;rdquo; despencam acentuadamente para menos de 1%.&lt;/p>
&lt;p>E a técnica que levou isso ao limite absoluto é a &lt;strong>QLoRA (Quantized LoRA)&lt;/strong>.
No QLoRA, os pesos do modelo-base, $W_0$, são quantizados ao máximo para 4 bits (no formato NF4: NormalFloat4) ao serem carregados na VRAM. As pequenas matrizes do LoRA, $A$ e $B$, por outro lado, são treinadas em BF16 (16 bits) para manter a precisão do cálculo.
Enquanto a quantização de 4 bits reduz o tamanho da VRAM exigido pelo modelo base para um quarto, ela usa uma técnica chamada &lt;strong>Paged Optimizers&lt;/strong> (otimizadores paginados) para descarregar (offload) temporária e automaticamente os estados do otimizador para a CPU RAM quando a VRAM está prestes a se esgotar. Isso tornou possível realizar o fine-tuning de modelos supergigantes como o Llama 3 70B até mesmo em uma única placa GPU de 24 GB de VRAM (como a RTX 4090).&lt;/p>
&lt;h2 id="33-deepspeed-zero-e-offloading">3.3 DeepSpeed ZeRO e Offloading
&lt;/h2>&lt;p>Quando múltiplas GPUs são usadas (ambiente multi-GPU), recorrer simplesmente à Paralelização de Dados (Data Parallelism) não resolverá os problemas de VRAM. Como cada GPU reserva uma cópia completa de todo o modelo, os limites de capacidade da VRAM individual não podem ser superados.&lt;/p>
&lt;p>O &lt;strong>ZeRO (Zero Redundancy Optimizer)&lt;/strong>, desenvolvido na biblioteca &lt;strong>DeepSpeed&lt;/strong> pela Microsoft, é uma técnica para particionar (shard) minuciosamente os parâmetros, gradientes e estados do otimizador de um modelo através de múltiplas GPUs. Por meio disso, a &amp;ldquo;soma total&amp;rdquo; da VRAM de múltiplas GPUs pode ser tratada como um único e gigante pool de memória.&lt;/p>
&lt;pre class="mermaid">
graph TD
subgraph &amp;#34;ZeRO Estágio 3 (Particionamento de Parâmetros)&amp;#34;
A[&amp;#34;GPU 0&amp;#34;] --&amp;gt; D[&amp;#34;Partição 0 (Armazena 1/3 de Pesos/Grads/Opts)&amp;#34;]
B[&amp;#34;GPU 1&amp;#34;] --&amp;gt; E[&amp;#34;Partição 1 (Armazena 1/3 de Pesos/Grads/Opts)&amp;#34;]
C[&amp;#34;GPU 2&amp;#34;] --&amp;gt; F[&amp;#34;Partição 2 (Armazena 1/3 de Pesos/Grads/Opts)&amp;#34;]
end
D[&amp;#34;Partição 0 (Armazena 1/3 de Pesos/Grads/Opts)&amp;#34;] &amp;lt;--&amp;gt; E[&amp;#34;Partição 1 (Armazena 1/3 de Pesos/Grads/Opts)&amp;#34;]
E[&amp;#34;Partição 1 (Armazena 1/3 de Pesos/Grads/Opts)&amp;#34;] &amp;lt;--&amp;gt; F[&amp;#34;Partição 2 (Armazena 1/3 de Pesos/Grads/Opts)&amp;#34;]
&lt;/pre>
&lt;ul>
&lt;li>&lt;strong>ZeRO Stage 1:&lt;/strong> Particiona os estados do otimizador por cada GPU.&lt;/li>
&lt;li>&lt;strong>ZeRO Stage 2:&lt;/strong> Particiona também os gradientes por cada GPU.&lt;/li>
&lt;li>&lt;strong>ZeRO Stage 3:&lt;/strong> Particiona até os próprios parâmetros (pesos) do modelo por cada GPU.&lt;/li>
&lt;/ul>
&lt;p>Além disso, usando a função &lt;strong>ZeRO-Offload&lt;/strong>, a manutenção dos estados do otimizador e os cálculos de atualização dos gradientes particionados pelo ZeRO podem ser movidos para a &lt;strong>memória da CPU (Offload)&lt;/strong> e executados pela CPU hospedeira em vez de usarem a GPU. Isso reduz extremamente o fardo sobre a VRAM da GPU e permite o treinamento de modelos massivos em ambientes com recursos limitados de GPU. Como os cálculos são realizados na CPU e os resultados retornam à GPU via PCIe, a velocidade de treinamento diminui, mas isso evita o pior cenário: &amp;ldquo;o treinamento travar devido à falta de memória&amp;rdquo;.&lt;/p>
&lt;hr>
&lt;h1 id="4-exemplo-de-implementação-hugging-face-accelerate-e-deepspeed">4. Exemplo de Implementação: Hugging Face Accelerate e DeepSpeed
&lt;/h1>&lt;p>Por fim, mostraremos exemplos simples de como você pode implementar o CPU offloading e otimizações de VRAM usando código Python.&lt;/p>
&lt;h2 id="41-offload-automático-com-device_mapauto-pelo-hugging-face">4.1 Offload Automático com &lt;code>device_map=&amp;quot;auto&amp;quot;&lt;/code> pelo Hugging Face
&lt;/h2>&lt;p>Usando as bibliotecas &lt;code>transformers&lt;/code> e &lt;code>accelerate&lt;/code> do Hugging Face, as camadas são alocadas e divididas automaticamente entre a GPU e a CPU ao carregar o modelo.&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;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">transformers&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">AutoModelForCausalLM&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">AutoTokenizer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;meta-llama/Llama-2-13b-hf&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Com device_map=&amp;#34;auto&amp;#34;, a parte que não couber na VRAM sofrerá offload para a CPU RAM&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># load_in_8bit=True quantiza os pesos para 8 bits, resultando em ainda mais economia de memória&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">AutoModelForCausalLM&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_pretrained&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model_id&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">device_map&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;auto&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">load_in_8bit&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">offload_folder&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;offload_dir&amp;#34;&lt;/span> &lt;span class="c1"># Se houver falta de CPU RAM, pode sofrer offload até para o disco (SSD)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&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>Ao executar este código, a biblioteca &lt;code>accelerate&lt;/code> em segundo plano analisa a capacidade ociosa do sistema (CPU RAM e VRAM) e organiza (Dispatch) as camadas da melhor maneira possível.&lt;/p>
&lt;h2 id="42-configuração-de-cpu-offloading-no-deepspeed-zero-2">4.2 Configuração de CPU Offloading no DeepSpeed (ZeRO-2)
&lt;/h2>&lt;p>Exemplo de arquivo de configuração (JSON) para ativar o CPU offloading usando DeepSpeed durante o treinamento.&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;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;fp16&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;enabled&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;zero_optimization&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;stage&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;offload_optimizer&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;device&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;cpu&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;pin_memory&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;allgather_partitions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;allgather_bucket_size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">2e8&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;overlap_comm&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;reduce_scatter&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;reduce_bucket_size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">2e8&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;contiguous_gradients&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;train_batch_size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">16&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;gradient_accumulation_steps&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">4&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&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>Nesta configuração, ao definir &lt;code>&amp;quot;cpu&amp;quot;&lt;/code> na opção &lt;code>offload_optimizer&lt;/code>, a retenção dos estados do otimizador (como Adam) — que consome uma imensa quantidade de VRAM — e o cálculo das atualizações são executados na CPU do sistema. Dessa forma, a VRAM da GPU pode ser dedicada exclusivamente para a tarefa mais importante: os cálculos de passe frontal e retropropagação (forward/backward) do modelo. Definir &lt;code>pin_memory: true&lt;/code> previne falhas de página (page faults) e permite acelerar ao máximo a transferência via PCIe entre CPU e GPU.&lt;/p>
&lt;hr>
&lt;h1 id="conclusão">Conclusão
&lt;/h1>&lt;p>A falta de memória de GPU (Out of Memory) no desenvolvimento de IA será um desafio contínuo e constante para os desenvolvedores à medida que os modelos continuam crescendo. No entanto, combinar perfeitamente um entendimento profundo do hardware (arquitetura) com as técnicas de otimização no âmbito de software/algoritmos, como explicado neste artigo, torna possível realizar o fine-tuning e a inferência de modelos gigantes localmente — algo que à primeira vista pareceria impossível.&lt;/p>
&lt;p>&lt;strong>Resumo das Contramedidas para Inferência:&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Quantização (INT4 / INT8 / FP8):&lt;/strong> Comprime drasticamente o tamanho do modelo, reduzindo a ocupação de VRAM.&lt;/li>
&lt;li>&lt;strong>CPU Offloading:&lt;/strong> Fuga das camadas que não cabem na VRAM para a memória do sistema (trade-off com a queda de velocidade pela largura de banda PCIe).&lt;/li>
&lt;li>&lt;strong>Otimização do Cache KV:&lt;/strong> Utiliza paginação (PagedAttention) ou quantização de cache junto com FlashAttention para preservar o comprimento de contexto (Context Length).&lt;/li>
&lt;li>&lt;strong>Utilização da Memória Unificada:&lt;/strong> Aproveita arquiteturas UMA (como Apple Silicon) utilizando grandes capacidades de memória unificada diretamente para inferência.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Resumo das Contramedidas para Treinamento:&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>PEFT (LoRA / QLoRA):&lt;/strong> Limita os parâmetros para treinamento e quantiza exaustivamente o modelo base.&lt;/li>
&lt;li>&lt;strong>Gradient Checkpointing:&lt;/strong> Descarta as saídas intermediárias da propagação frontal e as recalcula na retropropagação, mantendo o baixo consumo de VRAM em troca de maior tempo computacional.&lt;/li>
&lt;li>&lt;strong>ZeRO &amp;amp; CPU Offload (DeepSpeed):&lt;/strong> Supera o limite de VRAM fracionando os estados do otimizador e gradientes em múltiplas GPUs ou transferindo-os para a CPU (Offloading).&lt;/li>
&lt;/ol>
&lt;p>Aproveitando amplamente essas tecnologias inovadoras, maximize a performance no desenvolvimento de IA usando recursos limitados de hardware. Neste campo em evolução incrivelmente rápida, espera-se que surjam cada vez mais novos algoritmos de economia de memória no futuro. O segredo será verificar regularmente os desenvolvimentos recentes das bibliotecas e ser capaz de introduzi-las perfeitamente nas suas implementações.&lt;/p></description></item></channel></rss>