Ir para o conteúdo

23. Autoregressive Generation

Geração Autorregressiva de Imagens

Modelos de difusão dominam a geração de imagens desde 2020 — mas existe uma abordagem radicalmente diferente que ganhou força: tratar imagens como sequências de tokens discretos e gerá-las da mesma forma que modelos de linguagem geram texto.

Essa é a abordagem por trás da geração de imagens nativa do Gemini, do GPT-4o e de modelos como Chameleon (Meta) e LlamaGen.


O Problema: Como Tokenizar uma Imagem?

Texto é naturalmente discreto (palavras, subpalavras). Imagens são contínuas — pixels em \([0,255]^3\). Para usar geração autorregressiva, precisamos de um vocabulário visual.

A solução: VQ-GAN (Vector Quantization GAN)1 aprende um codebook de \(K\) vetores. O encoder mapeia qualquer patch de imagem para o vetor mais próximo do codebook — convertendo a imagem em uma grade de índices inteiros.


Geração Autorregressiva de Tokens

Com um codebook treinado, podemos representar qualquer imagem como uma sequência de \(N\) índices inteiros. Geramos então essa sequência exatamente como um LLM gera texto:

\[ p(t_1, t_2, \ldots, t_N) = \prod_{i=1}^{N} p(t_i \mid t_1, \ldots, t_{i-1}, \text{prompt}) \]

Cada token é gerado um de cada vez, condicionado em todos os anteriores e no prompt de texto.


MaskGIT: Geração Paralela por Mascaramento

A geração puramente autorregressiva é lenta: 1024 tokens = 1024 passes de modelo. O MaskGIT2 acelera isso com geração paralela iterativa:

  1. Começa com todos os tokens mascarados [MASK]
  2. A cada iteração, prediz todos os tokens simultaneamente (bidirecional!)
  3. "Revela" apenas os tokens com maior confiança
  4. Repete com menos tokens mascarados

Em apenas 8–12 iterações, gera 1024 tokens — contra 1024 iterações do AR puro.


Any-to-Any: Gemini, GPT-4o e Chameleon

O passo final é remover a distinção entre tokens de texto e tokens de imagem. Modelos any-to-any tratam tudo como uma sequência de tokens:

[TEXTO: "uma foto de"] [IMG_TOK_3742] [IMG_TOK_891] ... [IMG_TOK_5531] [TEXTO: "gato"]

O modelo Transformer padrão processa essa sequência misturada naturalmente.

Como Cada Modelo Implementa Isso

Modelo Tokenizador visual Geração Treinamento
Chameleon (Meta) VQ-VAE (8192 códigos) Autorregressivo puro Texto + imagem juntos desde o início
Gemini 2.0 (Google) Tokenizador proprietário AR + decoder de difusão Multimodal nativo
GPT-4o (OpenAI) Tokens visuais discretos AR + decoder de difusão Multimodal nativo
LlamaGen VQGAN (16384 códigos) AR com LLaMA Inicializa de LLaMA pré-treinado

AR vs. Difusão: Quando Usar Cada Um?

Geração Autorregressiva
  • Unifica texto e imagem na mesma arquitetura
  • Melhor para multimodal any-to-any
  • Aproveita toda a infraestrutura de LLMs
  • Escala bem com mais dados
  • Lento: 1 token por vez
Difusão (DDPM / Flow Matching)
  • Melhor qualidade de imagem isolada
  • Geração global coerente
  • Mais controle (guidance, cfg scale)
  • Mais rápido por imagem que AR
  • Não unifica com texto nativamente

A tendência atual: híbridos — um backbone LLM autorregressivo para entendimento e raciocínio, com um decoder de difusão para renderizar a imagem final com alta qualidade. É exatamente o que o GPT-4o faz.


Implementação: VQ-GAN + Transformer AR

import torch
import torch.nn as nn

# 1. Quantizador vetorial
class VectorQuantizer(nn.Module):
    def __init__(self, n_codes, d_code):
        super().__init__()
        self.codebook = nn.Embedding(n_codes, d_code)

    def forward(self, z):
        # z: (B, H, W, d_code) — latentes do encoder
        flat = z.view(-1, z.shape[-1])
        # Distâncias ao codebook
        dists = torch.cdist(flat, self.codebook.weight)
        indices = dists.argmin(dim=-1)            # índice do código mais próximo
        quantized = self.codebook(indices).view_as(z)
        # Straight-through estimator para backprop
        quantized_st = z + (quantized - z).detach()
        return quantized_st, indices.view(z.shape[:3])

# 2. Geração autorregressiva com modelo estilo GPT
class ImageGPT(nn.Module):
    def __init__(self, n_codes, seq_len, d_model, n_heads, n_layers):
        super().__init__()
        self.tok_emb = nn.Embedding(n_codes + 1, d_model)  # +1 para token BOS
        self.pos_emb = nn.Embedding(seq_len + 1, d_model)
        encoder_layer = nn.TransformerEncoderLayer(d_model, n_heads, d_model*4, batch_first=True)
        self.transformer = nn.TransformerEncoder(encoder_layer, n_layers)
        self.head = nn.Linear(d_model, n_codes)

    def forward(self, tokens):
        B, T = tokens.shape
        pos = torch.arange(T, device=tokens.device).unsqueeze(0)
        x = self.tok_emb(tokens) + self.pos_emb(pos)
        # Máscara causal
        mask = torch.triu(torch.ones(T, T, device=tokens.device), diagonal=1).bool()
        x = self.transformer(x, mask=mask)
        return self.head(x)  # logits sobre n_codes

    @torch.no_grad()
    def generate(self, prompt_tokens, n_new, temperature=1.0, top_k=2048):
        tokens = prompt_tokens.clone()
        for _ in range(n_new):
            logits = self(tokens)[:, -1, :] / temperature
            if top_k: logits[logits < logits.topk(top_k)[0][:,-1:]] = -float('inf')
            probs = logits.softmax(-1)
            next_tok = torch.multinomial(probs, 1)
            tokens = torch.cat([tokens, next_tok], dim=1)
        return tokens