O GPT-2 inteiro cabe em 174 linhas de TensorFlow. Lendo a fonte primária, linha por linha, contra tudo o que você aprendeu.
repos/Clonamos as fontes primárias. Elas não são para rodar — são para ler. Depois de doze apostilas, você tem o vocabulário para entender cada linha, e ler o original tem um valor que nenhuma explicação de segunda mão substitui.
| Repositório | O que tem | Roda aqui? |
|---|---|---|
gpt-2/ | o GPT-2 original, em TensorFlow 1.x. 558 linhas no total, das quais 174 são o modelo. | não — exige TF 1.15 e Python 3.7 |
GPT-3-Encoder/ | o tokenizador BPE em JavaScript, com encoder.json e vocab.bpe completos | sim — Node 24 está instalado |
gpt-3/ | não tem código: tem dados. Amostras do modelo de 175 B, estatísticas do corpus, análise de contaminação. | sim — é JSON |
Ele usa tensorflow.contrib, um módulo removido no TensorFlow 2.0 (2019). E o TF 1.15, última versão que o tem, não suporta Python acima do 3.7 — estamos no 3.14. Fazer rodar exigiria um ambiente Python 3.7 separado e uma versão de TF de 2019.
Não vale a pena, e o motivo é bom: os pesos são os mesmos. O gpt2 que você carrega com transformers é exatamente o modelo deste repositório, convertido de checkpoint TensorFlow para safetensors. Você já está rodando o GPT-2 original — só que numa implementação que funciona no seu Python.
O valor deste repositório é a leitura. E para isso ele é insubstituível.
Aqui está a função block de gpt-2/src/model.py, sem uma linha alterada:
def block(x, scope, *, past, hparams): with tf.variable_scope(scope): nx = x.shape[-1].value a, present = attn(norm(x, 'ln_1'), 'attn', nx, past=past, hparams=hparams) x = x + a m = mlp(norm(x, 'ln_2'), 'mlp', nx*4, hparams=hparams) x = x + m return x, present
Leia devagar e reconheça tudo:
x = x + a e x = x + m — as conexões residuais (apostila 05). O x nunca é destruído, só acrescido. O quadro-negro.norm(x, 'ln_1') dentro da chamada de attn, e a soma fora — isto é Pre-LN (apostila 05, seção 4). A diferença que permitiu 48 camadas está nesta ordem exata.nx*4 — o fator de expansão 4× do MLP, ali, literal.present — os Sete linhas. É o bloco Transformer inteiro.
def attention_mask(nd, ns, *, dtype): """1's in the lower triangle, counting from the lower right corner. Same as tf.matrix_band_part(tf.ones([nd, ns]), -1, ns-nd), but doesn't produce garbage on TPUs.""" i = tf.range(nd)[:,None] j = tf.range(ns) m = i >= j - ns + nd return tf.cast(m, dtype) def mask_attn_weights(w): _, _, nd, ns = shape_list(w) b = attention_mask(nd, ns, dtype=w.dtype) b = tf.reshape(b, [1, 1, nd, ns]) w = w*b - tf.cast(1e10, w.dtype)*(1-b) return w
w = w*b - 1e10*(1-b)
Onde b=1 (permitido), o peso passa intacto. Onde b=0 (futuro), subtrai-se 10¹⁰ — o que faz exp() devolver zero no softmax seguinte.
Note que eles usam −1e10, não −∞, exatamente como a apostila 04 descreve: infinito de verdade produz NaN em aritmética de ponto flutuante quando multiplicado por zero. Este é o tipo de detalhe que só a fonte primária ensina.
E repare no comentário deles: "doesn't produce garbage on TPUs". Há uma história inteira de depuração de hardware compactada nessa observação.
def gelu(x): return 0.5*x*(1+tf.tanh(np.sqrt(2/np.pi)*(x+0.044715*tf.pow(x, 3))))
A GELU exata usa a função de distribuição acumulada da normal, que é cara. Esta é a aproximação por tangente hiperbólica — e aquele 0.044715 é uma constante ajustada empiricamente. Está no código original, escrita na mão. Se você usar nn.GELU() do PyTorch sem argumentos, terá a versão exata, ligeiramente diferente desta.
c = conv1d(x, 'c_attn', n_state*3) q, k, v = map(split_heads, tf.split(c, 3, axis=2))
Uma única projeção para 3 × 768 = 2304, depois um corte em três. Exatamente o que a apostila 03 descreveu como detalhe de implementação — e aqui está a evidência.
O nome conv1d engana: leia a implementação e você verá que é uma matmul comum. É convolução de kernel 1, que é matematicamente idêntica a uma camada densa. Herança de convenção da época.
present = tf.stack([k, v], axis=1) if past is not None: pk, pv = tf.unstack(past, axis=1) k = tf.concat([pk, k], axis=-2) v = tf.concat([pv, v], axis=-2)
Empilha os
E ela só é possível porque o modelo é causal (apostila 11). Num encoder, não haveria o que cachear.
GPT-3-EncoderApesar do nome, é o tokenizador do GPT-2 — o GPT-3 reusou o mesmo. E é útil por dois motivos.
Primeiro, os arquivos de vocabulário estão ali, em texto puro:
encoder.json — o mapa token → ID, todos os 50 257.vocab.bpe — as 50 000 fusões, na ordem em que foram aprendidas. A primeira linha é a primeira fusão do treino do BPE.Você pode abrir esses arquivos num editor. É o merges.txt da apostila 01, e ver as primeiras linhas é ver a estatística do inglês.
Segundo, ele roda — em Node, que você tem. É útil quando você precisa contar tokens no navegador ou num serviço JavaScript, sem carregar Python.
gpt-3: dados, não códigoNão há implementação — o GPT-3 nunca foi aberto. Mas há coisas valiosas:
175b_samples.jsonl — saídas cruas do modelo de 175 bilhões. Compare com o que o gpt2-xl produz e você vê, com os próprios olhos, o que 112× mais parâmetros compram.overlap_frequency.md — análise de contaminação: quanto dos conjuntos de avaliação vazou para o corpus de treino. É um documento honesto e raro, e um bom lembrete de que benchmarks de LLM são mais frágeis do que parecem.dataset_statistics/ — composição do corpus..venv\Scripts\python.exe lab\10_original_code.py
model.py original lado a lado com a implementação PyTorch equivalente, para você ver a correspondência linha a linha.vocab.bpe e mostra as 20 primeiras fusões que o BPE aprendeu. As primeiras são as mais frequentes do inglês.Encoder.js via Node e compara.175b_samples.jsonl e mostra ao lado de uma geração do gpt2-xl com o mesmo tema.repos/gpt-2/src/model.py. Encontre a função norm e confirme, na fórmula, que é o LayerNorm da apostila 05. O que é o epsilon=1e-5?g e b. Que letras a apostila 05 usou para eles?repos/GPT-3-Encoder/vocab.bpe e olhe as 10 primeiras linhas. Que pares de caracteres o BPE achou mais frequentes no inglês? Isso te surpreende?model.py, encontre onde os logits são calculados no fim. Você consegue identificar o weight tying da apostila 02?attention_mask usa i >= j - ns + nd em vez de simplesmente i >= j. Por que essa complicação? (Dica: pense no que acontece quando past não é nulo e nd ≠ ns.)As fontes primárias dos conceitos deste capítulo. Os números do texto foram medidos no laboratório; as ideias vêm daqui.
Os termos técnicos têm aqui o sentido de aprendizado de máquina. O glossário do curso mostra esse sentido ao lado dos outros que as mesmas palavras têm na TI.
Você leu a fonte. A apostila 14 fecha o laboratório com a última peça prática:
como servir esses modelos por HTTP — e o que o TGI e o TEI fazem que o nosso servidor não faz.