Jag tillbringade några timmar med att gå igenom /karpathy/autoresearch repo rad för rad. Vinkeln "AI-agenter som forskar"-vinkeln är det som får all uppmärksamhet, men jag tycker det mest intressanta är vad som faktiskt finns i träningsskriptet och de tekniska besluten som gör sökloopen tajt. Det är en av de mest täta enkelfilsträningsuppläggen jag har läst. Låt mig börja med det som gör hela projektet möjligt: tidsbudgeten är fast vid 300 sekunders väggklocka. Inte fasta steg, inte fasta tokens, inga fasta flops. Väggklockor sekunder. Det här låter som en liten detalj men det är hela anledningen till att den autonoma loopen fungerar. Agenten kan göra modellen tre gånger större, halvera batchstorleken, byta in en helt annan arkitektur, och resultatet är fortfarande direkt jämförbart med alla andra experiment eftersom de alla fick exakt 5 minuters träning på samma GPU. Om du istället fixade steg skulle en större modell få färre gradientuppdateringar per sekund och du skulle straffa den orättvist. Om du fixade tokens skulle du ha samma problem. Att fixa väggtid innebär att du ställer rätt fråga: med denna hårdvara och så mycket tid, vilken är den bästa modellen du kan producera? Allt annat är en fri variabel. Agenten kan utforska hela Pareto-ytan av modellstorlek kontra genomströmning kontra konvergenshastighet utan att några av dessa kompromisser motverkas av utvärderingsprotokollet. Måttet är också noggrant utvalt. Det är bitar per byte, inte korsentropiförlust. Korsentropi beror på din ordförrådsstorlek. En modell med 32 000 tokens och en modell med 8 000 tokens kommer att ha mycket olika förlustvärden även om de komprimerar datan lika bra. BPB normaliserar bort detta genom att summera korsentropin per token i nats, summera utf-8-byte längderna för måltokens och konvertera nats-per-byte till bits-per-byte. Så även om agenten ändrar något som påverkar den effektiva tokendistributionen, förblir jämförelsen rättvis. Dessa två val, fast väggtid och en vokabulär-invariant metrik, förvandlar vad som skulle vara en rörig och ojämförlig sökning till ett rent optimeringsproblem. Nu till själva modellen. det är en GPT men med en massa moderna knep som är värda att förstå. Först, RMSnorm överallt. på blockinmatningarna (pre-norm), och även på frågor och nycklar precis före attention dot-produkten. Denna QK-norm är viktig eftersom utan den kan normerna q och k växa obegränsat under träning, vilket gör att attention logits skärps och softmax blir mättnade. Att normalisera Q och K håller dotprodukterna inom ett stabilt intervall oavsett hur djupt nätverket är eller hur träningsdynamiken utvecklas. uppmärksamheten i sig är FA 3, laddad via kernels-biblioteket. Det använder Varunneals implementation på Hopper (sm_90) och faller tillbaka på en community-byggnation på äldre GPU:er. uppmärksamhetsmönstret är "SSSL" vilket betyder tre lager av skjutande fönsteruppmärksamhet (fönster = halva sekvenslängden) följt av ett lager av full kausal uppmärksamhet, som upprepas. Det här är det gles till täta mönstret du ser i Mistral och Gemma2. De lokala uppmärksamhetslagren är beräkningsmässigt billiga eftersom uppmärksamhetsmatrisen är bandad, och det periodiska globala lagret låter information flöda över hela kontexten. Med 8 lager och ett 4-teckensmönster får du lager 0,1,2 lokalt, lager 3 globalt, lager 4,5,6 lokalt, lager 7 globalt. Det sista lagret tvingas globalt oavsett mönster. Värdeinbäddningen är subtil och jag tycker att den inte uppskattas. Varje annat lager får sin egen inbäddningstabell, helt separat från huvudtoken-inbäddningen, som mappar token-id:n direkt till värdedimensionvektorer. Dessa blandas in i uppmärksamhetsvärdena via en inlärd grind: v = v + 2 * sigmoid(W_gate @ x:32) * ve. grindvikten är noll-initialiserad, så sigmoid(0) = 0,5 gånger 2 ger 1,0, vilket är en neutral startpunkt. Genom att träna över kan modellen lära sig att förstärka eller undertrycka värdeinbäddningen per huvud baserat på de första 32 dimensionerna av det dolda tillståndet. detta kommer från ResFormer-linjen och intuitionen är att det ger uppmärksamhet en direkt genväg till tokenidentitet. Värdevektorerna kan bära information om "vilken token som befinner sig på denna position" utan att den informationen behöver överleva de kvarvarande strömtransformationerna från tidigare lager. Det är i princip en hoppanslutning från inmatningen direkt till uppmärksamhetsvärdena, låst så att modellen kan avgöra när det är användbart. Det finns också per-lager lärbara skalärer på restströmmen: x = lambda_residi * x + lambda_x0i * x0, där x0 är den normaliserade inbäddningen från lager 0. Varje lager kan självständigt kontrollera hur mycket det lyssnar på den löpande resterna jämfört med den ursprungliga ingången. De återstående lambdas börjar på 1,0, x0 lambdas på 0,1. Detta är en mjuk version av idén om "avskilda rester". I en standardtransformator är restströmmen summan av alla tidigare lagerutgångar och den blir alltmer förorenad ju djupare du kommer. Att ge varje lager tillgång till den rena ursprungliga inbäddningen innebär att det inte behöver lära sig att "ångra" tidigare lager för att återställa lågnivåinformation. Logits är softcapade till 15 via tanh(logits/15)*15, vilket förhindrar att modellen blir övermodig tidigt i träningen när representationerna fortfarande är brusiga. Men ärligt talat är den mest intressanta delen av hela filen optimeraren. MuonAdamW är en kombinerad optimerare som skickar olika uppdateringsregler baserat på parametergrupp. Embeddings (token-embeddings, värdeembeddingar, unembedding head) och per-lager-skalärer får standard AdamW med olika inlärningshastigheter för varje grupp. Spridningen är vild. Inbäddning av LR är 0,6, avbäddning av LR är 0,004, det är en skillnad på 150 gånger, och det är avsiktligt. Embedding-matrisen ser varje enskild token och behöver uppdateras aggressivt. Avbäddningsmatrisen är en linjär prob på den slutliga representationen och gynnas av stabilitet. inbäddnings-, värdeinbäddnings- och avbäddningsinlärningshastigheterna skalas alla med (d_model / 768)^(-0,5) vilket är en MuP-inspirerad korrigering. När modellens bredd förändras justeras dessa inlärningshastigheter för att hålla funktionsinlärningsdynamiken skalningsinvariant. De skalära inlärningshastigheterna för lambdas per lager hanteras separat och får inte denna skalning. 2D-viktmatriserna i transformatorn, uppmärksamhetsprojektioner och mlp-vikter, får Muon, och här blir det verkligen intressant. Muon tar gradienten, applicerar Nesterov-rörelsemängd och kör sedan en Newton-Schulz-iteration för att approximera den polära dekompositionen av gradientmatrisen. den polära dekompositionen faktoriserar en matris G i G = U * S där U är ortogonal och S är symmetrisk positivt semidefinit. muon beräknar U, den närmaste ortogonala matrisen till gradienten, och använder den som uppdateringsriktning. Newton-Schulz-versionen består av 5 steg. för höga matriser (fler rader än kolumner), A = X^T @ X och sedan X -> aX + X @ (bA + cA^2). för breda matriser är A = X @ X^T och sedan X -> aX + (bA + cA^2) @ X. Koefficienterna hårdkodas från en förberäkning. De kallar det "Polar Express." Hela paketet kompileras till en enda fusionerad kärna via torch.compile. Varför är detta viktigt? För viktmatriser är Frobenius-normgradienten (den som Adam och SGD använder) geometriskt felaktig. Den "korrekta" brantaste nedstigningsriktningen för en viktmatris är den som minimerar förlusten med villkoret att uppdateringen har enhetsspektralnorm, inte enhets Frobeniusnorm. Den ortogonala polära faktorn ger dig exakt detta. I praktiken innebär det att Muon gör mycket större effektiva uppdateringar eftersom det inte slösar steglängd på att skala de enskilda värdena. Det roterar dem bara. Det är därför myon konvergerar avsevärt snabbare än Adam på transformatorviktmatriser. Muon behåller momentumbuffertar per element (samma form som parametrarna, staplade över varje formgrupp), men till skillnad från Adam spårar den inte sekundmomenten per element. De andra momentskattningarna är per rad eller per kolumn efter ortogonalisering, inte per element. Det är där NorMuon kommer in. ovanpå basmyonen finns NorMuon, ett variantreduktionssystem. Efter ortogonalisering beräknar den per rad (eller per kolumn beroende på aspektförhållande) andra momentuppskattningar, upprätthåller ett exponentiellt glidande medelvärde av dessa och skalar om uppdateringen så att varje utdata dimension får sin egen adaptiva steglängd. Det är i princip idén om Adams adaptivitet, men applicerad i det ortogonaliserade koordinatsystemet snarare än i det råa parameterutrymmet. Viktminskningen är också icke-standard. Den är "försiktig", vilket betyder att den endast avtar parametrar där myonuppdateringens riktning stämmer överens med parametertecknet: mask = (g * parametrar) >= 0. Detta undviker det kända felläget där viktminskning pressar parametrarna mot noll mot uppdateringens önskemål, vilket kan destabilisera träningen. En liten detalj jag uppskattade: efter det allra första träningssteget anropar koden gc.collect(), gc.freeze(), gc.disable() för att helt stänga av Pythons skräpsamlare. Pythons GC körs periodiskt och orsakar ~500 ms stopp. när din totala budget är 300 sekunder och varje steg kanske 300 ms, kostar en slumpmässig GC-paus nästan 2 träningssteg. De triggar manuellt gc.collect() var 5000:e steg som en kompromiss. Det här är den typen av saker man bara lär sig genom att profilera verkliga träningspass och märka mystiska genomströmningsfall. De första 11 stegen (0 till 10) räknas inte heller in i tidsbudgeten. det är uppvärmningen där torch.compile gör sin grej och CUDA-kärnor blir JIT:ade. Utan denna uteslutning skulle olika experiment få olika mängder "verklig" träning beroende på hur lång tid kompilering tar för just den modellkonfigurationen. Återigen ett designval som verkar litet men är avgörande för att göra experimenten jämförbara. Nu zooma ut. Den faktiska autoresearch-loopen är: agenten läser program.md (en markdown-fil som beskriver dess jobb), modifierar tåg .py, commitar, kör i 5 minuter, kontrollerar om val_bpb förbättrats, behåller eller återställs, upprepar. program.md säger uttryckligen "ALDRIG SLUTA." springer agenten på obestämd tid tills människan dödar den. ~12 experiment per timme, ~100 över natten medan du sover. ...