Tópicos populares
#
Bonk Eco continues to show strength amid $USELESS rally
#
Pump.fun to raise $1B token sale, traders speculating on airdrop
#
Boop.Fun leading the way with a new launchpad on Solana.
gastei algumas horas a percorrer o repositório /karpathy/autoresearch linha por linha.
a abordagem dos "agentes de IA a fazer pesquisa" é o que está a receber toda a atenção, mas acho que a parte mais interessante é o que está realmente dentro do script de treino e as decisões de engenharia que tornam o ciclo de pesquisa eficiente. é uma das configurações de treino em arquivo único mais densas que já li.
deixe-me começar com a coisa que torna todo o projeto possível: o orçamento de tempo é fixo em 300 segundos de relógio. não passos fixos, não tokens fixos, não flops fixos. segundos de relógio. isso pode parecer um detalhe menor, mas é a razão inteira pela qual o ciclo autónomo funciona. o agente pode tornar o modelo 3x maior, reduzir o tamanho do lote pela metade, trocar por uma arquitetura completamente diferente, e o resultado ainda é diretamente comparável a todos os outros experimentos porque todos tiveram exatamente 5 minutos de treino na mesma gpu. se você fixasse os passos, um modelo maior receberia menos atualizações de gradiente por segundo e você estaria penalizando-o injustamente. se você fixasse os tokens, teria o mesmo problema. fixar o tempo de parede significa que você está a fazer a pergunta certa: dado este hardware e este tempo, qual é o melhor modelo que você pode produzir? tudo o resto é uma variável livre. o agente pode explorar toda a superfície de pareto do tamanho do modelo vs throughput vs velocidade de convergência sem que nenhuma dessas trocas seja confundida pelo protocolo de avaliação.
a métrica também é cuidadosamente escolhida. são bits por byte, não perda de entropia cruzada. a entropia cruzada depende do tamanho do seu vocabulário. um modelo com 32k tokens e um modelo com 8k tokens terão valores de perda muito diferentes, mesmo que comprimam os dados igualmente bem. bpb normaliza isso somando a entropia cruzada por token em nats, somando os comprimentos de byte utf-8 dos tokens alvo, e convertendo nats-por-byte em bits-por-byte. assim, mesmo que o agente mude algo que afete a distribuição efetiva de tokens, a comparação permanece justa. essas duas escolhas, tempo de parede fixo e uma métrica invariável ao vocabulário, transformam o que seria uma busca desordenada e incomparável em um problema de otimização limpo.
agora o modelo em si. é um GPT, mas com um monte de truques modernos que valem a pena entender. primeiro, RMSnorm em todo lugar. nas entradas do bloco (pré-normalização), e também em consultas e chaves logo antes do produto escalar de atenção. essa coisa de QK-norm é importante porque sem ela as normas de q e k podem crescer de forma ilimitada durante o treino, fazendo com que os logits de atenção se agudizem e o softmax se sature. normalizar q e k mantém os produtos escalares em uma faixa estável, independentemente de quão profundo o modelo é ou como as dinâmicas de treino evoluem. a atenção em si é FA 3, carregada através da biblioteca kernels. usa a implementação de varunneal no hopper (sm_90) e recai para uma construção comunitária em gpus mais antigas. o padrão de atenção é "SSSL", o que significa três camadas de atenção de janela deslizante (janela = metade do comprimento da sequência) seguidas por uma camada de atenção causal completa, repetindo. este é o padrão esparso-para-denso que você vê no mistral e gemma2.
as camadas de atenção local são computacionalmente baratas porque a matriz de atenção é em faixa, e a camada global periódica permite que a informação flua através de todo o contexto. com 8 camadas e um padrão de 4 caracteres, você obtém camadas 0,1,2 locais, camada 3 global, camadas 4,5,6 locais, camada 7 global. a última camada é forçada a ser global, independentemente do padrão.
a coisa da incorporação de valores é sutil e acho que subestimada. cada camada recebe sua própria tabela de incorporação, completamente separada da incorporação de token principal, que mapeia ids de token diretamente para vetores de dimensão de valor. esses são misturados nos valores de atenção através de um portão aprendido: v = v + 2 * sigmoid(W_gate @ x:32) * ve. o peso do portão é inicializado em zero, então sigmoid(0) = 0.5, vezes 2 dá 1.0, que é um ponto de partida neutro. ao longo do treino, o modelo pode aprender a amplificar ou suprimir a incorporação de valor por cabeça com base nas primeiras 32 dimensões do estado oculto. isso vem da linha de trabalho ResFormer e a intuição é que dá à atenção um atalho direto para a identidade do token. os vetores de valor podem carregar informações sobre "qual token está nesta posição" sem que essa informação tenha que sobreviver às transformações do fluxo residual das camadas anteriores. é essencialmente uma conexão de salto da entrada diretamente para os valores de atenção, controlada para que o modelo possa decidir quando é útil.
também há escalares aprendíveis por camada no fluxo residual: x = lambda_residi * x + lambda_x0i * x0, onde x0 é a incorporação normalizada da camada 0. cada camada pode controlar independentemente quanto escuta o residual em execução versus a entrada original. os lambdas residuais começam em 1.0, os lambdas x0 começam em 0.1. esta é uma versão suave da ideia de "residual disentangled". em um transformador padrão, o fluxo residual é uma soma de todas as saídas das camadas anteriores e fica cada vez mais poluído à medida que você vai mais fundo. dar a cada camada acesso à incorporação original limpa significa que ela não precisa aprender a "desfazer" camadas anteriores para recuperar informações de baixo nível. os logits são suavemente limitados a 15 via tanh(logits/15)*15, o que impede que o modelo fique excessivamente confiante no início do treino, quando as representações ainda estão ruidosas.
mas, honestamente, a parte mais interessante de todo o arquivo é o otimizador. MuonAdamW é um otimizador combinado que despacha diferentes regras de atualização com base no grupo de parâmetros. as incorporações (incorporação de token, incorporações de valor, cabeça de desincorporação) e escalares por camada recebem AdamW padrão com diferentes taxas de aprendizado para cada grupo. a variação é selvagem. a taxa de aprendizado da incorporação é 0.6, a taxa de aprendizado da desincorporação é 0.004, isso é uma diferença de 150x, e é intencional. a matriz de incorporação vê cada token e precisa atualizar agressivamente. a matriz de desincorporação é uma sonda linear na representação final e se beneficia da estabilidade. as taxas de aprendizado de incorporação, incorporação de valor e desincorporação são todas escaladas por (d_model / 768)^(-0.5), que é uma correção inspirada no muP. à medida que a largura do modelo muda, essas taxas de aprendizado se ajustam para manter as dinâmicas de aprendizado de características invariantes em escala. as taxas de aprendizado escalares para os lambdas por camada são tratadas separadamente e não recebem essa escalagem.
as matrizes de peso 2D no transformador, projeções de atenção e pesos de mlp, recebem Muon, e é aqui que fica genuinamente interessante. muon pega o gradiente, aplica momento de nesterov, e então executa uma iteração de newton-schulz para aproximar a decomposição polar da matriz de gradiente. a decomposição polar fatoriza uma matriz G em G = U * S onde U é ortogonal e S é simétrica positiva semi-definida. muon calcula U, a matriz ortogonal mais próxima do gradiente, e usa isso como a direção de atualização. a iteração de newton-schulz é de 5 passos. para matrizes altas (mais linhas do que colunas), A = X^T @ X então X -> aX + X @ (bA + cA^2). para matrizes largas, A = X @ X^T então X -> aX + (bA + cA^2) @ X. os coeficientes são codificados a partir de uma pré-computação. eles chamam isso de "polar express." tudo isso compila para um único kernel fundido via torch.compile.
por que isso importa? porque para matrizes de peso, o gradiente da norma de frobenius (o que adam e sgd usam) está geometricamente errado. a "direção" correta de descida mais íngreme para uma matriz de peso é aquela que minimiza a perda sujeita à restrição de que a atualização tenha norma espectral unitária, não norma de frobenius unitária. o fator polar ortogonal dá exatamente isso. na prática, isso significa que muon faz atualizações efetivas muito maiores porque não está desperdiçando tamanho de passo na escala dos valores singulares. ele apenas os rotaciona. é por isso que muon converge significativamente mais rápido do que adam em matrizes de peso de transformador. muon mantém buffers de momento por elemento (mesmo formato que os parâmetros, empilhados em cada grupo de formato), mas ao contrário de adam, não rastreia momentos de segundo por elemento. as estimativas do segundo momento são por linha ou por coluna após a ortogonalização, não por elemento. é aí que entra o NorMuon.
em cima do muon base, há o NorMuon, um esquema de redução de variância. após a ortogonalização, ele calcula estimativas do segundo momento por linha (ou por coluna, dependendo da razão de aspecto), mantém uma média móvel exponencial dessas, e reescala a atualização para que cada dimensão de saída receba seu próprio tamanho de passo adaptativo. é essencialmente a ideia de adaptividade do adam, mas aplicada no sistema de coordenadas ortogonalizado em vez do espaço de parâmetros bruto. a decaimento de peso também é não padrão. é "cauteloso", significando que apenas decai parâmetros onde a direção de atualização do muon concorda com o sinal do parâmetro: máscara = (g * params) >= 0. isso evita o modo de falha conhecido onde o decaimento de peso empurra parâmetros em direção a zero contra os desejos da atualização, o que pode desestabilizar o treino.
um pequeno detalhe que apreciei: após o primeiro passo de treino, o código chama gc.collect(), gc.freeze(), gc.disable() para desligar completamente o coletor de lixo do python. o GC do python roda periodicamente e causa paradas de ~500ms. quando seu orçamento total é de 300 segundos e cada passo leva talvez 300ms, uma pausa aleatória do GC custa quase 2 passos de treino. eles acionam manualmente gc.collect() a cada 5000 passos como um compromisso. esse é o tipo de coisa que você só aprende ao perfilar execuções de treino reais e notar quedas misteriosas de throughput.
os primeiros 11 passos (0 a 10) também não são contados para o orçamento de tempo. essa é a fase de aquecimento onde torch.compile faz seu trabalho e os kernels CUDA são JIT'd. sem essa exclusão, diferentes experimentos teriam diferentes quantidades de "treino real" dependendo de quanto tempo a compilação leva para aquela configuração de modelo particular. novamente, uma escolha de design que parece pequena, mas é crítica para tornar os experimentos comparáveis.
agora, amplie a visão. o verdadeiro ciclo de autoresearch é: o agente lê program.md (um arquivo markdown que descreve seu trabalho), modifica train.py, comete, roda por 5 minutos, verifica se val_bpb melhorou, mantém ou reverte, repete. program.md diz explicitamente "NUNCA PARE." o agente roda indefinidamente até que o humano o mate. ~12 experimentos por hora, ~100 durante a noite enquanto você dorme.
...
Top
Classificação
Favoritos
