Dentro un LLM: guida interattiva a tokenizzazione, attenzione e generazione

A glowing neural network pipeline turning a prompt into token chips, vectors, attention arcs, transformer layers, and a probability chart

🇬🇧 Read this article in English

Fai una domanda a ChatGPT. Risponde. Tra quelle due azioni succede qualcosa di genuinamente strano, e quasi nessuno lo spiega. Le tue parole vengono fatte a pezzetti, trasformate in numeri, moltiplicate per matrici gigantesche, votate, e riassemblate, un token alla volta, finché il modello non decide che ha finito.

Questo articolo è quella spiegazione, ed è interattivo. Il widget qui sotto è un modello funzionante del viaggio dal prompt alla risposta. Cliccaci, muovi i cursori, poi torna su per il codice e i commenti.

Tutto in otto passi

Il visualizzatore ti accompagna nella pipeline vera che un large language model esegue ogni volta che premi invio. Non una metafora. Le fasi reali: template del prompt, tokenizzazione, embedding, codifica posizionale, self-attention, strati transformer impilati, predizione del token successivo, e generazione autoregressiva. I numeri sono finti per renderli stabili e didattici, ma le forme e la logica sono quelle vere.

Usa le frecce. Clicca i token. Trascina il cursore della temperatura nel passo 7 e guarda il modello passare dal noioso al caotico. Poi leggi sotto cosa fa davvero ogni pezzo.

Nessun dato esce dal tuo browser. Tutto gira localmente nell\u2019iframe.

Passo 1 e 2: le tue parole diventano ID di token

Il modello non legge lettere. Legge interi. Un tokenizzatore spezza il tuo prompt in chunk di caratteri frequenti e cerca ognuno in un vocabolario fisso di circa 50.000-200.000 voci. Lo spazio iniziale conta: blu è un token diverso da blu. È anche il motivo per cui i modelli sono notoriamente pessimi a contare le lettere in una parola. Non le vedono mai.

Questo è il tokenizzatore del widget qui sopra, spogliato all’osso:

function tokenize(text){
  // cattura chunk tipo-parola, numeri, a-capo, punteggiatura isolata
  const raw=text.match(/ ?[A-Za-zÀ-ÖØ-öø-ÿ']+| ?[0-9]+|\n|[^\sA-Za-zÀ-ÖØ-öø-ÿ0-9]/g)||[];
  const parts=[];
  raw.forEach(w=>parts.push(...splitLong(w)));   // parole lunghe vengono divise
  return parts.map(t=>({text:t, id:hash(t)%100256}));
}

I tokenizzatori reali usano il Byte-Pair Encoding (BPE): partendo dai byte grezzi, fondono le coppie adiacenti più frequenti in voci del vocabolario. Il principio è identico. Testo in, ID fuori. Da qui in poi il modello vede solo numeri.

Passo 3: ogni ID diventa un vettore

Ogni ID di token indicizza una riga di una matrice gigante appresa. Quella riga è l’embedding del token, un vettore di migliaia di numeri. Pensalo come coordinate in uno spazio dei significati: parole usate in contesti simili finiscono vicine. Il trucco da festa che citano tutti è re - uomo + donna ≈ regina, e funziona davvero perché la geometria codifica la semantica.

Nel widget mostro 16 dimensioni per token come celle colorate. I modelli reali ne usano 4.000-16.000.

function embedding(id){
  const r=mulberry32(id*2654435761>>>0);  // RNG deterministico per ID
  return Array.from({length:D},()=>r()*2-1);  // D=16 qui, migliaia nella realtà
}

Passo 4: l’ordine viene timbrato

Il cane morde l'uomo e l'uomo morde il cane tokenizzano nello stesso insieme di token. L’attenzione da sola non ha senso dell’ordine. Quindi ogni vettore viene combinato con una codifica posizionale che dice in che punto della sequenza si trova il token. L’approccio classico somma sinusoidi di frequenze diverse; i modelli moderni come Llama e Claude usano RoPE, che ruota i vettori di un angolo proporzionale alla posizione.

function posEnc(pos){
  return Array.from({length:D},(_,i)=>{
    const freq=1/Math.pow(80, Math.floor(i/2)*2/D);
    return i%2===0 ? Math.sin(pos*freq) : Math.cos(pos*freq);
  });
}

Passo 5: i token si parlano (la parte famosa)

Questa è la self-attention, il meccanismo a cui il transformer deve il nome. Ogni token emette tre cose: una query (cosa sto cercando?), una key (cosa contengo?), e un value (cosa passo avanti?). Il peso di attenzione tra due token è quanto la query di uno matcha la key dell’altro. Una maschera causale impedisce ai token di guardare il futuro.

function attnMatrix(tokens,head){
  // per ogni token i, punteggia ogni token j fino a i incluso
  // i token futuri (j>i) prendono -1e9 (mascherati prima della softmax)
  const n=tokens.length, M=[];
  for(let i=0;i<n;i++){
    const row=[];
    for(let j=0;ji){row.push(-1e9);continue;}
      // ogni testa impara una sua abitudine di punteggio
      row.push(scoreFor(head,i,j));
    }
    M.push(softmax(row.slice(0,i+1)));  // normalizza sui token visibili
  }
  return M;
}

Decine di queste teste girano in parallelo per strato, e ognuna impara un’abitudine diversa. Clicca le quattro teste nel widget: una segue la parola precedente, una si àncora sul primo token (un fenomeno reale chiamato attention sink), una trova significati affini, una si distribuisce su tutto. Niente di tutto questo è progettato a mano. Emerge dall’addestramento.

Token colorati collegati da archi traslucidi di attenzione di spessore variabile, illustrazione della self-attention in un transformer
Ogni arco è un token che presta attenzione a uno precedente. Più spesso significa peso maggiore.

Passo 6: impila in profondità

Un solo giro di attenzione non basta. Il suo risultato entra in una piccola rete feed-forward (la MLP) che trasforma ogni token da solo, e poi l’intero blocco si ripete, decine di volte. Gli strati iniziali colgono grammatica e sintassi. Quelli profondi assemblano fatti, stile e il piano della risposta. Circa due terzi dei parametri di un modello vivono nelle MLP.

Il trucco che permette di impilare così in profondità è il residual stream: ogni strato si limita a ritoccare un totale corrente invece di sostituirlo. GPT-3 impila 96 di questi blocchi. Ogni blocco ha la stessa forma, ognuno con i suoi pesi appresi.

Passo 7 e 8: indovina, campiona, ripeti

Dopo l’ultimo strato il modello ha un compito: predire il token successivo. Moltiplica il vettore finale per una matrice di unembedding per ottenere un punteggio (un logit) per ogni token del vocabolario, poi la softmax trasforma i punteggi in probabilità.

function softmaxT(logits,T){
  const t=Math.max(T,0.05);
  const m=Math.max(...logits);
  const e=logits.map(l=>Math.exp((l-m)/t));
  const s=e.reduce((a,b)=>a+b,0);
  return e.map(x=>x/s);
}

La temperatura T è la manopola. T bassa affila la distribuzione verso il favorito (deterministico, spento). T alta la appiattisce (creativo, a volte fuori di testa). T verso 0 è decoding greedy: scegli sempre il token in cima. I sistemi reali applicano anche filtri top-k o top-p (nucleus) per tagliare la coda di assurdità prima di campionare.

Poi il token campionato viene incollato in fondo al testo e l’intera pipeline riparte. Quel ciclo, un token alla volta, è la risposta che vedi arrivare in streaming. Si ferma quando il modello emette un token di stop speciale. Questa è la generazione autoregressiva, ed è esattamente il motivo per cui le risposte lunghe richiedono più tempo e costano di più: ogni nuovo token è un passaggio completo attraverso tutti gli strati.

Perché conta

Quando capisci la pipeline, gran parte della “magia” degli LLM smette di essere magia. Non contano le lettere perché non le vedono mai. Allucinano perché a ogni passo campionano da una distribuzione di probabilità, non recuperano un fatto. Arrivano parola per parola perché generano davvero un token alla volta. La finestra di contesto è un limite su quanti token entrano in un singolo passaggio. La temperatura non è creatività, è solo quanto è piatta la distribuzione di probabilità.

Niente di tutto questo è segreto. È tutto nel paper originale Attention Is All You Need del 2017. Ma leggere il paper e sentirlo nelle mani sono cose diverse. Quindi torna a giocherellare col widget, alza la temperatura nel passo 7, e guarda la distribuzione appiattirsi. Quella, meccanicamente, è la “creatività”.

Il sorgente completo del visualizzatore è un singolo file HTML autonomo, senza build, senza dipendenze. Se vuoi leggere tutte le 1.000 righe o rubartelo per le tue lezioni, è lì nel sorgente della pagina dell’iframe.

English|Italiano