1 pontos por GN⁺ 2024-02-25 | 1 comentários | Compartilhar no WhatsApp
  • Reproduz o fluxo de inferência do GPT-2 em SQL usando apenas PostgreSQL e pgvector, da tokenização aos blocos Transformer e à geração do próximo token
  • Um LLM generativo é mais próximo de uma função determinística que produz as mesmas probabilidades de tokens candidatos para a mesma entrada; o ponto em que a resposta muda é a etapa probabilística de seleção do token candidato
  • A implementação expressa em consultas SQL e tabelas o tokenizador BPE do GPT-2, 50.257 tokens, embeddings de 768 dimensões, contexto de 1.024 tokens, 12 blocos, 12 cabeças de attention e feedforward baseado em GELU
  • Foram necessárias adaptações práticas ao ambiente de banco de dados, como a falta de suporte a propriedades Unicode em regex do PostgreSQL e a limitação do EXP com valores muito pequenos
  • No exemplo, a partir de "Happy New Year! I wish you", são gerados 10 tokens e a saída é "Happy New Year! I wish you all the best in your new year!"; no ambiente do autor, levou 2 minutos e 44 segundos

Criando um pipeline de inferência do GPT-2 em SQL

  • O ChatGPT respondeu que SQL não seria adequado para implementar um modelo de linguagem de grande porte, mas foi implementado um pipeline de inferência do GPT-2 em PostgreSQL SQL
  • A explicação de implementação usada como referência é GPT in 60 Lines of NumPy, de Jay Mody, e os mesmos componentes foram transpostos para tabelas e consultas de banco de dados
  • Um LLM generativo pode ser visto como uma função do tipo llm(prompt: str) -> list[tuple[str, float]]
    • A entrada é um prompt de texto
    • A saída é um array de candidatos à próxima string e suas probabilidades
    • Se a matemática interna e os parâmetros forem os mesmos, a mesma entrada retorna o mesmo resultado
  • O motivo de produtos como o ChatGPT poderem dar respostas diferentes à mesma pergunta está menos no modelo em si e mais na seleção probabilística da próxima palavra/token

Loop de geração de texto

  • O processo de geração tem a estrutura de transformar o prompt em um array de tokens, chamar o modelo repetidamente para escolher o próximo token e anexá-lo ao final do prompt
  • O fluxo básico é composto pelas seguintes etapas
    • Converter a string em um array de IDs de token com tokenize(prompt)
    • gpt2(tokens) calcula as probabilidades para 50.257 tokens
    • select_next_token(candidates) escolhe o próximo token
    • Adicionar o token escolhido ao array
    • Parar em condições como número fixo de tokens, timeout ou stopword
    • Restaurar o array de tokens para string com detokenize(tokens)
  • A sequência acumulada de tokens assim pode se tornar um texto em linguagem natural com propriedades que parecem gramática, sintaxe, significado e raciocínio

Implementando o tokenizador BPE em SQL

  • Antes de entrar na rede neural, o texto precisa virar uma lista de números, mas usar diretamente code points Unicode torna o espaço de tokens e o comprimento ineficientes
  • O GPT-2 usa uma variação de Byte pair encoding
    • O vocabulário de tokens usa 50.257 code points
    • Inclui sequências de bytes UTF-8 e o token “end of text”
    • Começa com 256 tokens de byte e depois adiciona novos tokens a partir de pares adjacentes frequentes
    • Essa fusão é repetida 50.000 vezes para chegar a 50.256 tokens, e por fim é adicionado o token end-of-text
  • O tokenizador do GPT-2 tem uma camada extra que mapeia bytes para caracteres de string, e esse mapeamento está definido em encoder.py do OpenAI GPT-2
  • Na implementação em SQL, o vocabulário de tokens baixado da OpenAI foi colocado na tabela tokenizer, e o mapeamento byte-caractere foi armazenado na tabela encoder
  • O exemplo "Mississippilessly" começa com bytes individuais em um recursive CTE e faz repetidamente a melhor fusão possível entre pares adjacentes
    • No exemplo, o número de tokens cai de 17 para 5
    • Em vez de usar o espaço de cerca de 150 mil code points do Unicode, usa o espaço de cerca de 50 mil tokens do GPT-2
  • Ao processar várias palavras, o GPT-2 divide o texto com regex e faz as fusões dentro de cada palavra
    • O PostgreSQL não oferece suporte a propriedades de caracteres Unicode em regex, então a regex original do GPT-2 foi parcialmente modificada
    • Essa modificação pode ter prejudicado o suporte adequado a Unicode
  • "PostgreSQL is great" é convertido pelo tokenizador SQL em [6307, 47701, 318, 1049]
    • Os clusters de tokens são Post, greSQL, Ġis, Ġgreat
    • Ġ representa um espaço

Embeddings e janela de contexto

  • IDs de token não são usados diretamente nos cálculos do modelo; eles são convertidos em vetores de embedding
  • O GPT-2 faz embedding separado de token e de posição
    • WTE é o word token embedding e é uma matriz 50257×768
    • WPE é o word position embedding e é uma matriz 1024×768
  • Para cada posição do token, soma-se o vetor de WTE com o vetor de WPE para formar o vetor de entrada da etapa seguinte
  • Como WPE tem apenas 1.024 posições, o número máximo de tokens que pode ser usado em um prompt do GPT-2 é 1.024
    • Esse número corresponde à janela de contexto do LLM
    • É um hiperparâmetro definido no projeto do modelo e não muda com treinamento
  • A implementação em SQL usa pgvector
    • Também seria possível em SQL puro definindo operações vetoriais diretamente sobre arrays, mas o desempenho seria pior
    • A versão inicial funcionava com funções puramente SQL, mas era lenta

Expandindo self-attention em consultas SQL

  • O núcleo do Transformer é o mecanismo de self-attention, baseado no artigo de 2017 Attention is all you need
  • A attention faz com que os vetores de token influenciem uns aos outros, permitindo que informações do início do prompt cheguem ao vetor final
  • A implementação do GPT-2 usa 12 conjuntos de matrizes Q, K, V
    • Cada conjunto é uma attention head
    • Cada head tem 64 dimensões
    • c_attn é uma transformação linear 768×2304, e o resultado é um vetor de 2304 dimensões com Q, K, V empilhados horizontalmente
    • Os pesos e bias ficam armazenados nas tabelas c_attn_w e c_attn_b
  • Antes do cálculo de attention, é aplicada layer normalization
    • Os parâmetros de scale e shift ficam nas tabelas ln_1_g e ln_1_b
  • Em causal self-attention, aplica-se uma causal mask para impedir que tokens posteriores influenciem tokens anteriores
    • Os candidatos ao próximo token do modelo são determinados no fim a partir do último embedding
    • O fluxo de informação precisa apontar para o último vetor, e os valores intermediários do último vetor não podem influenciar vetores anteriores
  • Na implementação em SQL, para evitar o problema de o EXP do PostgreSQL falhar com números muito pequenos no cálculo de softmax, entradas menores que -745.13 são tratadas como 0
  • Graças à causal mask, mesmo que um novo token seja anexado ao prompt, os resultados já calculados para os tokens anteriores não mudam
    • A implementação original do GPT-2 aproveita essa propriedade
    • A implementação em SQL não reutiliza isso por simplicidade

Multi-head attention e conexões residuais

  • Os resultados de attention das 12 heads têm 64 dimensões cada e são empilhados horizontalmente para voltar a 768 dimensões
  • Em seguida, a saída de attention é projetada por uma transformação linear treinada armazenada em c_proj_w e c_proj_b
  • O resultado da multi-headed attention recebe novamente a soma da entrada original
    • Essa conexão residual é uma técnica incluída no artigo original do Transformer
    • Foi apresentada como um desenho para aliviar problemas de vanishing gradient e exploding gradient durante o treinamento

Etapa feedforward e bloco Transformer

  • Depois da attention, vem uma rede neural feedforward
  • A etapa feedforward do GPT-2 é composta por um perceptron multicamadas de 3 camadas
    • As dimensões são 768 → 3072 → 768
    • A função de ativação usada é GELU
  • Os parâmetros de transformação linear são armazenados nas seguintes tabelas
    • mlp_c_fc_w, mlp_c_fc_b
    • mlp_c_proj_w, mlp_c_proj_b
  • A entrada do feedforward também é normalizada antes com os parâmetros de ln_2
    • ln_2_g e ln_2_b armazenam scale e shift
  • Uma conexão residual que soma de volta a entrada também é aplicada à saída do feedforward
  • Essa combinação de attention + feedforward forma um bloco, e o GPT-2 encadeia 12 blocos como um pipeline
    • Cada bloco tem seu próprio conjunto de parâmetros treinados
    • Em SQL, os blocos são conectados com um recursive CTE
  • A saída do último bloco é normalizada novamente com os parâmetros ln_f

Voltando ao próximo token

  • No resultado final, o vetor de 768 dimensões da última posição é o embedding do próximo token
  • Para transformá-lo de volta em token, reutiliza-se a matriz WTE usada no embedding inicial dos tokens
  • Em geral, a inversão exata não é possível
    • O embedding previsto pode não ser exatamente igual a uma linha específica de WTE
    • Por isso, calcula-se o dot product com cada embedding de token para encontrar os tokens mais próximos
  • O resultado do dot product entre WTE e o embedding previsto gera 50.257 pontuações, ou seja, logits
  • Essas pontuações passam por softmax e viram probabilidades
    • top_n é o número de candidatos do topo
    • temperature é o valor que controla a distribuição de probabilidades
    • Quanto maior a temperature, maior a chance de um token fora do primeiro lugar ser escolhido, e menos previsível fica a inferência
  • No exemplo "PostgreSQL is great", os 5 principais candidatos ao próximo token são os seguintes
    • Ġfor
    • ,
    • .
    • Ġat
    • Ġto
  • Quando a temperature muda para 0.5, 1 e 2, a distribuição softmax de probabilidade dos mesmos candidatos muda

Resultado real da inferência e código

  • O SQL final repete o processo de escolher tokens segundo a probabilidade e anexá-los ao prompt
  • O modelo em si é determinístico; o único elemento não determinístico está no sorteio do token
  • A configuração do exemplo é a seguinte
    • Prompt: "Happy New Year! I wish you"
    • Número de tokens gerados: 10
    • Temperature: 2
    • top_n: 1
    • Uso de SETSEED(0.20231231)
  • No ambiente do autor, a consulta levou 2 minutos e 44 segundos para executar
  • O resultado de saída foi "Happy New Year! I wish you all the best in your new year!"
  • A consulta e o código de instalação estão no repositório GitHub quassnoi/explain-extended-2024

1 comentários

 
GN⁺ 2024-02-25
Comentários do Hacker News
  • Lindo. Eu também estava cavando uma toca de coelho parecida com SQLite, mas ainda não tinha chegado ao ponto de puxar até redes neurais
    Foi inspirado pela série de aulas makemore[0], e depois de mais ou menos 1 hora ela passa do método de contagem para redes neurais, que foi mais ou menos até onde eu cheguei
    Dividir isso em um modelo relacional é um exercício realmente ótimo
    [0] https://www.youtube.com/watch?v=PaCmpygFfXo

    • Se você continuar, a rede neural acaba produzindo exatamente a mesma tabela do método de contagem e, ao gerar, dá exatamente o mesmo resultado
  • É uma boa demo, mas a explicação de mascaramento causal no texto parece misturar treinamento e inferência
    O mascaramento causal serve para impedir que se “espie” tokens futuros durante o treinamento e, em arquiteturas do tipo GPT, também para impor autorregressão durante a inferência
    Na inferência, de qualquer forma, só o último token é usado, então esse token presta atenção à sequência inteira de entrada; portanto, o próximo token não é determinado apenas pelo embedding do último token

  • Fico me perguntando se isso representa com precisão o loop driver do GPT: tokenizar o prompt, obter as probabilidades de 50257 tokens com gpt2(tokens), escolher o próximo token, anexá-lo à lista de tokens, verificar a condição de parada e, no fim, destokenizar
    Mas isso faz a máquina de estados parecer estar implementando o algoritmo do pintor Shlemiel, o que me faz questionar o custo computacional intrínseco do trabalho de geração

    • Entendo que a janela de contexto de que as pessoas falam em modelos de linguagem grandes significa que existe um limite máximo de tokens mantidos, e os tokens mais antigos são descartados
      Essa janela é uma janela deslizante
    • Isso mesmo, esse é o loop, e toda a mágica está dentro da função gpt2
    • Essa é uma parte bem pequena do algoritmo
      É mais ou menos só mostrar como os tokens gerados são reunidos em uma frase
  • Material relacionado: A GPT in 60 Lines of NumPy - https://news.ycombinator.com/item?id=34726115 - fevereiro de 2023, 146 comentários

    • Esse material já aparece no começo do texto
  • Em um contexto parecido, implementei o GPT inteiro usando funções de planilha, e também fiz um tutorial em vídeo para acompanhar
    https://spreadsheets-are-all-you-need.ai/

    • O primeiro vídeo é excelente
      Eu acho LLMs bem legais, mas nunca precisei aprender profissionalmente como elas funcionam de fato, e esse vídeo de 10 minutos me ensinou mais do que anos lendo comentários obscuros no HN e textos rasos da mídia tradicional
      Ver aquela quantidade absurda de números de ponto flutuante empilhados esperando cálculo também tornou muito mais intuitivo entender por que essa tecnologia consome tanta GPU
    • Planilhas são uma forma natural de explicar LLMs
      Também parece que daria para explicar bem o processo de treinamento calculando a derivada de cada parâmetro em cada exemplo de treino e mostrando explicitamente como isso se mapeia para aquele parâmetro
  • Muito bom. Há 1 ano isso ainda parecia uma espécie de mágica, e agora está sendo explicado tão bem, quase a ponto de até uma criança conseguir acompanhar

    • Essa mágica não começou há 1 ano
      O modelo explicado no texto é o GPT-2, lançado no começo de 2019
    • Não chega a ser “a ponto de uma criança conseguir acompanhar”
      Para entender este texto direito, é preciso ter uma base sólida em ciência da computação, e o próprio título já é pouco acessível para 99% da humanidade
  • Eu vinha evitando completamente GPT e LLMs, e esse método parece conseguir produzir certa fluência na saída de texto, mas não parece ir até a capacidade de interpretar perguntas e respondê-las
    Queria saber se existe algum post de blog simples ou curso que explique como isso realmente funciona, ou que mostre um motor de brinquedo em Python
    O material educacional que vi até agora tende a focar em como usar as plataformas, e não tanto em como elas funcionam por dentro

  • Curiosamente, o aprendizado de máquina moderno não exige completude de Turing
    Mesmo assim estamos considerando a possibilidade de AGI, então seria bem interessante se a conclusão fosse que completude de Turing não é necessária

    • Parece que completude de Turing é necessária
      Por um motivo simples: eu consigo acompanhar mentalmente a execução de um código Turing-completo
    • A inferência de tokens em si não é Turing-completa, mas, se a saída puder criar efeitos colaterais, por exemplo modificar o prompt da próxima iteração, aí a história é completamente diferente
  • O texto foi excelente, e a explicação de cada componente estava clara e bem completa, então foi uma ótima leitura
    Mas apertei por engano “+ expand source” e, depois de ver aquele monstro impressionante, passei a concordar com o ChatGPT quando diz que “SQL não é adequado para implementar modelos de linguagem grandes”

    • Eu também cliquei e não consegui descobrir como recolher de novo
  • A afirmação de que “Unicode comum não combina bem com redes neurais” não é verdadeira. Basta olhar para ByT5
    O que o texto chama de “alfabeto” normalmente é chamado de vocabulário, e, se você usar bytes UTF-8 como vocabulário, passa a ter 256 tokens em vez de 149186
    O ByT5 faz exatamente isso

    • A questão não é que não funcione de jeito nenhum, e sim que não funciona tão bem quanto outras abordagens que temos
      O fato de que os modelos com melhor desempenho do mercado todos usam tokenização é a prova disso
      Não é segredo que tokenização é, no fundo, algo meio hacky, e que o ideal seria conseguir eliminá-la de alguma forma no futuro (https://twitter.com/karpathy/status/1657949234535211009)
      Em princípio, dá para compensar as limitações da tokenização em nível de bytes com modelos maiores e contexto maior, mas, na prática, treinar um modelo com o mesmo nível de inteligência exige muito mais recursos
      Claro, também existem tarefas específicas em que a tokenização acaba até prejudicando a inteligência, como contar o número de letras em uma palavra