Skip to content

LLM Internals

How an LLM decides to terminate a response

An LLM generally decides a response is complete by using specific internal markers or external constraints. These mechanisms ensure the model doesn't generate text indefinitely or end a sentence abruptly before it is finished.

The most common way an LLM "knows" to stop is through a special vocabulary symbol called an end-of-sequence token (often denoted as ⟨EOS⟩, ⟨/s⟩, or [EOS]).

  • During Training: These markers are inserted at the very end of sequences in the training dataset.
  • During Generation: Because the LLM is trained to predict the next token, it will eventually predict the ⟨EOS⟩ token as the most probable next step when the context suggests a natural conclusion. Once this symbol is generated, the sampling process immediately terminates.

In practical applications (like chatbots), developers often implement rules to stop generation regardless of the model's internal state to manage costs and response times.

  • Maximum Length: The model is forced to stop once it reaches a predetermined number of tokens.
  • Time/Resource Limits: Decoding may be terminated after a certain time limit to ensure the system remains responsive for users.
  • Beam Search Completion: In search algorithms that explore multiple paths (beam search), the process stops when a specified number of "complete" hypotheses (those ending in an EOS token) have been found.

Some advanced systems monitor the model's output in real-time to decide when to stop:

  • Probability Thresholds: Generation can be halted if the probability of the next predicted token falls below a certain "confidence" threshold.
  • Repetition Detection: A specialized module can trigger a stop if it detects the model has entered a loop, repeating the same phrases or tokens beyond a predefined limit.

In summary, while the core LLM "engine" typically uses EOS tokens to signal completion based on patterns learned during training, the broader tools you interact with (like ChatGPT) layer on length and time constraints to ensure efficient and coherent performance.

How an LLM summarizes text

Summarization is an intrinsic ability of a Large Language Model (LLM), arising naturally from its fundamental training process rather than from additional external modules. By learning to guess the next word in a sequence across trillions of tokens of text, the model acquires a vast amount of language knowledge and world facts. Because text often contains condensed versions of information, the model naturally learns the patterns required to distill long sequences into shorter ones.

This capability is refined in the training phases that follow pre-training (fine tuning), where summarization is cast as a task: the model is shown pairs of inputs and desired outputs
(e.g., input "Summarize this: ….", output [desired Summary]; input “...tl;dr”, output [desired Summary]”).

Chain of Thought (Reasoning Chain)

Chain of Thought (also called reasoning chain) is a prompting method where the LLM is instructed to generate intermediate reasoning steps before providing the final answer.

The model is run just once, and all intermediate reasoning steps are generated in a single prediction along with the final answer. The LLM decomposes complex problems into smaller, sequential reasoning steps, making the process more transparent and increasing accuracy for tasks like math or logical deduction.

CoT can be triggered by providing a few demonstrations of step-by-step problem solving (few-shot CoT) or by simply adding the instruction "Let's think step by step" to the prompt (zero-shot CoT).

CoT emerges from pretraining alone, it is elicited via prompting — no special training strictly required. The quality and reliability of the reasoning depends heavily on what training the model received, though.

Modern LLMs are specifically trained to do CoT well (“reasoning models”). In modern LLMs, a prompt phrase like "let's think step by step" mostly nudges the presentation (more explicit step delimiters, more signposting of "why" at each stage) rather than changing the underlying reasoning path.

Consequence of the Prompt (Training-free)

For standard, off-the-shelf LLMs, reasoning chains are often triggered entirely by the prompt without any specific training for that task. This is achieved through:

  • Zero-shot CoT Prompting: This activates the model's "thinking" ability simply by adding a sentence like "Let’s think step by step" to the end of a prompt. This instruction provokes the LLM to generate reasoning steps before reaching a final answer without needing any prior examples in the prompt.
  • Few-shot CoT Prompting: The prompt includes a few demonstration examples that show a problem-solving process broken into intermediate steps. The LLM learns from these demonstrations to mimic the underlying thought process.

The "reasoning" is considered an emergent ability. The model indeed learned general knowledge about the world during pre-training, and the prompt acts as a way to "activate and reorganize" that knowledge efficiently. This worked because pretraining on massive text corpora already exposes a model to enormous amounts of step-by-step reasoning (math solutions, code walkthroughs, explanatory writing), so the capability to produce reasoning-like text is latent in the base model. Prompting just elicits it.

Importantly, this emergent behavior was observed to appear reliably only at sufficient model scale — smaller models often didn't benefit from CoT prompting the way larger ones did, suggesting it's tied to capabilities that emerge from scale/pretraining rather than requiring a dedicated training phase.

Constructed at Training Time (Training-based)

Reliable, high-quality, self-correcting CoT (the kind seen in current "reasoning models", i.e., modern LLMs): requires dedicated post-training.

This is typically done with Reinforcement Learning against verifiable correctness signals, which is a distinct training phase — related to but not identical to the alignment training focused on helpfulness/harmlessness/honesty. Alignment training in the RLHF-for-safety sense (as opposed to RL-for-reasoning) mostly shapes how the model reasons about sensitive topics (calibration, refusal behavior, avoiding harmful reasoning chains) rather than whether it can reason step-by-step at all.

Example: Solving a math theorem with CoT

Take a simple but illustrative case — proving that √2 is irrational.

A CoT might look like:

  1. Assume for contradiction that √2 is rational. Then √2 = a/b for integers a, b with no common factors (fraction in lowest terms).
  2. Squaring both sides: 2 = a²/b², so a² = 2b².
  3. This means a² is even, which implies a itself must be even (since odd² is always odd).
  4. So write a = 2k. Substituting: (2k)² = 2b² → 4k² = 2b² → b² = 2k².
  5. This means b² is even, so b is also even.
  6. But if both a and b are even, they share a common factor of 2 — contradicting our assumption that a/b was in lowest terms.
  7. Contradiction reached, so the original assumption is false. Therefore √2 is irrational.

This works well as one continuous CoT because each step depends tightly on the previous one, the "state" (what we know so far) is small enough to hold in a single context, and there's no need to explore multiple independent branches or verify against external tools.

LLM Architecture

FFN (feedforward network)

Il vettore del punto:

  1. viene proiettato in uno spazio a dimensione molto più alta (tipicamente 4x, tramite una prima matrice)
  2. passa attraverso una non-linearità (ReLU/GELU/SwiGLU)
  3. viene riproiettato nella dimensione originale (seconda matrice)

il risultato è sommato al punto di partenza.

L'interpretazione intuitiva che ne deriva è quella di una memoria chiave-valore:

  1. La prima matrice contiene una "chiave" per ogni riga. Una chiave descrive “proprietà” specifiche nel punto in ingresso. Il prodotto tra la riga ed un punto è uno scalare e risponde alla domanda "quanto è vero, per questo punto, che ha la proprietà X?" (es. "ha a che fare con il calcio", "è un participio passato", "è l'inizio di un nome proprio").
  2. La non-linearità (ReLU, ecc.) azzera tutti i match deboli, cioè lascia attive solo le chiavi che corriapondono ai "veri riconoscimenti".
  3. la seconda matrice contiene le "direzioni predefinite" corrispondenti che vengono sommate al punto quando la chiave si attiva (la proprietà è presente).

Le chiavi e le direzioni sono apprese durante il training e restano fisse in fase di inferenza — non cambiano in base al contesto, cambia solo quanto vengono attivate.

Perché la prima matrice "riconosce proprietà (chiavi)" Ogni riga della prima matrice è un vettore nello stesso spazio del punto in ingresso. Il prodotto scalare tra quella riga ed il punto in ingresso è, geometricamente, una misura di quanto i due vettori puntano nella stessa direzione: se il punto in ingresso è molto "allineato" con quella riga, il prodotto scalare è alto; se sono quasi ortogonali, il prodotto scalare è vicino a zero (o negativo). Quindi ogni riga della prima matrice definisce una direzione specifica: durante il training quella riga si è "sintonizzata" per puntare esattamente nella direzione che i punti aventi una certa proprietà tendono ad assumere (es."ha a che fare con il calcio", "è un participio passato", "è l'inizio di un nome proprio"). La proprietà è tipicamente astratta e non intuitiva: è stata identificata nel processo di training come direzione che minimizza l’errore.

Perché la seconda matrice "sono direzioni predefinite da sommare" Qui il ruolo è simmetrico ma inverso: mentre la prima matrice legge (misura quanto una proprietà è presente), la seconda matrice scrive (dice cosa fare se quella proprietà è presente). La riga k della seconda matrice è un vettore appreso durante il training che rappresenta "l'aggiustamento da applicare al punto ogni volta che la chiave k si attiva": se la chiave "participio passato" si accende, allora la riga di valore corrispondente potrebbe spingere il punto in una direzione dello spazio che il modello ha imparato essere utile per rappresentare quel concetto nei layer successivi.

Le due matrici sono completamente disaccoppiate: la riga k di W1 (la chiave, usata per la rilevazione) e la riga k di W2 (il valore, usato per la scrittura) sono due vettori appresi in modo indipendente, senza alcun vincolo che li leghi. Non c'è nessuna regola che dica "se la chiave punta in direzione D1, il valore deve anch'esso puntare verso D1" — potrebbero essere completamente scorrelate, e nella pratica lo sono quasi sempre.

Questo è esattamente ciò che rende l'FFN qualcosa di più espressivo di una semplice "amplificazione": non sta dicendo "se vedo D1, rendi D1 più forte", sta implementando una vera e propria regola condizionale appresa: "se rilevo la proprietà X (misurata dal prodotto scalare con la chiave), allora applica l'aggiustamento Y (la riga di valore)" — dove X e Y possono essere concettualmente del tutto diversi.

Un esempio concreto per rendere l'idea: una chiave potrebbe attivarsi fortemente quando riconosce "negazione seguita da un verbo al participio passato" (es. "non è stato fatto") — questa è la direzione D1 che il prodotto scalare rileva. Il valore associato a quella chiave, però, potrebbe spingere il punto verso una direzione dello spazio che codifica "questo è probabilmente il soggetto di una forma passiva" — un concetto grammaticale completamente diverso da D1, utile per i layer successivi del Transformer. La chiave fa da "sensore", il valore fa da "azione conseguente" — e il modello, durante il training, è libero di imparare qualsiasi coppia sensore→azione risulti utile per ridurre la loss finale, per quanto le due possano sembrare scorrelate a un osservatore umano.

E’ precisamente questo disaccoppiamento chiave/valore che permette a un singolo layer FFN di implementare migliaia di regole "se X allora Y" indipendenti in parallelo — non un semplice filtro che amplifica ciò che è già presente, ma una vera e propria lookup table appresa durante il training: condizione → conseguenza.

Attention

X input matrix: ogni riga è un token nello spazio degli embedding.

  • Numero di righe = seq_len (lunghezza richiesta analizzata: inferiore alla lunghezza massima del contesto).
  • Numero colonne = d_model (dimensione degli embedding).

WQ, WK, WV: matrici di pesi, della stessa dimensione.

  • Numero di righe = d_model (dimensione degli embedding).
  • Numero di colonne = d_head (d_model diviso per il numero di attention heads).

Q = X x WQ, K = X * WK , V = X * WV

  • Numero di righe = seq_len
  • Numero colonne = d_head
  • Ogni riga è un token nello spazio degli “embedding compressi” alla dimensione d_head.

Ogni riga di Q e di K è associata ad un token di ingresso; definisce una direzione nello spazio compresso degli embedding (query) ed il corrispondente contributo del token in quella direzione (key). Direzione e contributo non sono fissati dal solo token ma dal token nella frase, in quanto si ottengono a partire da X.

Ogni riga di V è associata ad un token di ingresso; ne rappresenta il contenuto informativo da trasportare in output ed è calcolato in parallelo a Q e K, in modo indipendente da essi. Anche questo valore informativo dipende dall’intera frase, in quanto è calcolato a partire da X.

Lo “score” di ogni token di ingresso si calcola come Scores = Softmax(Q * KT/ sqrt(d_head)). Alla matrice Q * KT si applica una maschera triangolare inferiore prima del calcolo di Softmax. In altre parole, un token di ingresso produce una query nello spazio degli embedding compressi; ogni token della sequenza (incluso se stesso) produce una key nello stesso spazio; la riga di Q·Kᵀ che corrisponde al token di ingresso quantifica quanto sono allineate con la sua query le key di tutti i token ammessi dalla maschera (se stesso e i token precedenti).

Il valore di attention per ogni token è ottenuto come Scores * V. Ogni riga corrisponde ad un token ed il numero di colonne è d_head (embedding compressi).

Ogni attention head produce una matrice Scores * V, usando una terna di matrici WQ, WK, WV diversa da quella di ogni altra head. Queste matrici sono affiancate per colonne, ottenendo una matrice intermedia con seq_len e d_model colonne. Le righe di questa matrice hanno dimensione d_model ma non sono embedding, in quanto sono state costruite concatenando degli embedding compressi.

La matrice intermedia è quindi moltiplicata per una matrice di pesi WO appresa in training, di dimensione seq_len x d_model. La matrice risultante ha ancora dimensione seq_len x d_model e viene sommata alla matrice di ingresso X.

L’effetto è che ad ogni riga di X viene sommato un “piccolo spostamento riga”, che rappresenta il valore di attention dei token diversi da quello della riga.

Number of parameters

Usiamo Llama 3.1 8B.

  • d_model = 4096
  • 32 attention head
  • quindi d_head = 4096 / 32 = 128.

Attention layer

Wq, Wk, Wv — ciascuna di dimensione d_model × d_head = 4096 × 128= 524.288 parametri per matrice. * Per una testa: 524.288 × 3 = 1.572.864 parametri. * Per 32 teste: 1.572.864 × 32 = 50.331.648 parametri (≈ 50,3M)

Wo — l'unica matrice condivisa, dimensione d_model × d_model = 4096 × 4096= 16.777.216 parametri (≈ 16,8M).

Totale: 50.331.648 + 16.777.216 = 67.108.864 parametri (≈ 67,1M per layer)

FFN

Llama 3.1 usa una variante gated (SwiGLU) con 3 matrici invece di 2, e una dimensione intermedia reale di 14.336 (non un semplice ×4 di 4096): 4096 × 14.336 = 58.720.256 parametri per matrice.

Con 3 matrici (gate, up, down): 58.720.256 × 3 = 176.160.768 parametri (≈ 176,2M per layer).

Confronto

Modulo Parametri Quota
Attention (Wq+Wk+Wv×32 teste + Wo) ≈ 67,1M ~28%
FFN ≈ 176,2M ~72%

FFN ha circa 2,6 volte i parametri dell'attention.

Moltiplicando (67,1M + 176,2M) ≈ 243,3M per i 32 layer di Llama 3.1 8B si ottiene ≈ 7,8 miliardi di parametri — molto vicino agli 8B dichiarati nel nome del modello (la differenza residua è dovuta agli embedding di ingresso/uscita e alle normalizzazioni, non conteggiati qui).