
By the end of this article, you will be able to:
If you use VS Code, you've experienced this countless times:
You type cons, and the editor suggests console.log(). It feels natural — the editor analyzed your context and picked the most likely completion out of a few candidates.
Now, scale that up by a factor of one hundred million:
This is an LLM. At its core, it's a supercharged autocomplete engine that predicts the next word.
When you type "The capital of France is" into ChatGPT, what it does is logically identical to what your editor does when you type cons:
Input: "The capital of France is" → Predict next token → "Paris"
Input: "The capital of France is Paris" → Predict next token → "."
Input: "The capital of France is Paris." → Predict next token → <stop>Every single step does the same thing: given the text so far, guess what token (word chunk) is most likely to come next.
It sounds simple, right? But this simple mechanism, amplified by massive data and a powerful architecture, produces what we perceive as "intelligence."
Intuitively, you might think an LLM predicts one character at a time. But the actual unit is called a Token — a word chunk.
Consider this English sentence:
"unbelievable"
Token breakdown might be:
["un", "bel", "ievable"]And another:
"I love eating apples"
Token breakdown might be:
["I", " love", " eating", " apples"]Notice the pattern: high-frequency words tend to be whole tokens, while rare words get split into smaller pieces. This is like code splitting in front-end builds — popular libraries are bundled as one chunk, while rarely-used ones are split into smaller pieces.
A tokenizer works a bit like "longest match" in string processing:
Vocabulary (partial):
┌──────────────┬───────┐
│ Token │ ID │
├──────────────┼───────┤
│ "I" │ 105 │
│ " love" │ 2048 │
│ " apples" │ 7890 │
│ "un" │ 401 │
│ "believable" │ 15023 │
└──────────────┴───────┘
Input text → scan left to right → match longest token in vocabulary → output ID sequenceThe LLM never sees text — it sees a sequence of numbers:
"I love eating apples"
↓ tokenize
[105, 2048, 7890]
↓ feed into model
Model processes
↓ output
Predicted next token ID
↓ lookup in vocabulary
Get the wordThis is like a front-end source map — the mapping between source code and compiled output. The tokenizer is the LLM's source map.
Tokenization directly affects LLM behavior:
When you chat with ChatGPT long enough and it starts forgetting what you said earlier, that's your token budget running out. It's like the browser's localStorage 5MB quota — once full, old data gets dropped, not compressed.
When you type "The weather today is", the LLM doesn't just say "nice." Internally, it does this:
Input: "The weather today is"
Model output → probability distribution over the entire vocabulary:
"nice" ████████████████████████ 0.78
"hot" ██████ 0.12
"cold" ██ 0.04
"terrible" █ 0.02
"okay" █ 0.01
... (49,995 other tokens) 0.03 (combined)In JavaScript terms, the LLM outputs something like a giant Map<Token, number>:
// Pseudocode: the essence of LLM prediction
function predictNextToken(context: string): Map<string, number> {
// In reality this is far more complex, but at its heart
// it just assigns a score to every candidate word
return new Map([
["nice", 0.78],
["hot", 0.12],
["cold", 0.04],
["terrible", 0.02],
["okay", 0.01],
// ... tens of thousands more
]);
}If you always pick the highest score, the LLM becomes boring:
Input: "Tell me a joke"
Always pick top score → same joke every timeIt's like asking a coworker the same question and getting a verbatim identical response — accurate, but robotic.
To make responses more varied and human-like, LLMs use sampling to introduce randomness. The two knobs that control this randomness are Temperature and Top-K.
Temperature is a value between 0 and 2. Here's what it does intuitively:
Temperature = 0 → Always pick the highest score, deterministic (like Math.max())
Temperature = 0.3 → Favor high scores, occasionally pick second-best (conservative)
Temperature = 0.7 → Fairly even distribution, occasional surprises (default)
Temperature = 1.5 → Flattened distribution, wildly creative (brainstorming mode)Technically, temperature divides each logit (raw score) before softmax, either "sharpening" or "flattening" the probability distribution:
// Pseudocode: how temperature affects probabilities
function applyTemperature(logits: number[], temperature: number): number[] {
// logits are the model's raw output scores
// temperature closer to 0 → gaps amplified → high scores get higher
// temperature larger → gaps shrunk → distribution flattens
return logits.map(score => score / temperature);
}Think of it like your product manager's requirement change frequency:
0.1: the spec is locked in, never wavers0.7: normal pace, occasional tweaks1.5: a new direction every day — creative but hard to shipTemperature adds randomness, but without boundaries, low-probability tokens could get picked — producing incoherent or garbled output.
Top-K and Top-P define the boundary of "pick only from the highest-probability candidates."
Probability distribution: "nice" 0.40 / "okay" 0.25 / "great" 0.15 / "fine" 0.08 / ...
Top-K=3: only sample from "nice", "okay", "great"
Top-P=0.80: accumulate until ≥ 0.80 → "nice" + "okay" + "great" + "fine" = 0.88 ≥ 0.80, sample from these 4This is like debounce and throttle in front-end development — both strike a balance between "allowing change" and "preventing chaos":
Now let's connect everything. The complete LLM generation flow is:
Step 1: "The capital of France" → compute distribution → apply temperature → Top-K/Top-P filter → sample → "is"
Step 2: "The capital of France is" → compute distribution → apply temperature → Top-K/Top-P filter → sample → "Paris"
Step 3: "The capital of France is Paris" → compute distribution → apply temperature → Top-K/Top-P filter → sample → "."
Step 4: "The capital of France is Paris." → next token is <EOS> (end marker) → stopThis can be expressed in simplified JavaScript:
async function generateResponse(prompt: string): Promise<string> {
const tokens: number[] = tokenizer.encode(prompt);
const maxTokens = 4096;
for (let i = 0; i < maxTokens; i++) {
// 1. Forward pass: given the token sequence, output scores for each candidate
const logits = await model.forward(tokens);
// 2. Apply temperature
const scaled = logits.map(l => l / temperature);
// 3. Softmax to convert to probabilities
const probs = softmax(scaled);
// 4. Top-K / Top-P filtering
const filtered = topKTopPFilter(probs, topK, topP);
// 5. Sample from the filtered distribution
const nextToken = sample(filtered);
// 6. If end-of-sequence token, stop
if (nextToken === EOS_TOKEN_ID) break;
// 7. Append new token to sequence, continue looping
tokens.push(nextToken);
}
return tokenizer.decode(tokens);
}This loop is the entirety of what an LLM does when "thinking." It doesn't reason. It doesn't query a database. It doesn't call tools. It just repeats "guess the next word" until it guesses the stop marker.
Counterintuitive, right? Such a simple mechanism can pass exams, write code, and translate languages. This brings us to the architecture that makes it intelligent.
LLMs didn't always use Transformers. Early language models used RNNs (Recurrent Neural Networks). They worked like reading a book word by word:
RNN processing "I went to the store and bought some fruit":
"I" → update state → "went" → update state → "to" → update state → ...The problem? It forgets, just like a person would:
"I met a friend in New York ten years ago, and he... (5000 words later) ...where is he now?"
RNN: Who? What was I reading about earlier?An RNN is like a person with only short-term memory — by the time they reach the end of a book, chapter one is a blur. This is like rendering a list without keys in React — the framework can't track which element maps to which data, so it brute-forces the diff.
In 2017, the Transformer introduced a radically new mechanism: Self-Attention.
The core idea: when the model processes a sentence, let every word directly look at every other word in that sentence, assigning attention weights based on relevance.
Sentence: "The cat chased the mouse, but it ran very fast"
Self-attention lets the model figure out what "it" refers to:
"it" ↔ "cat" relevance: 0.72 ← highest!
"it" ↔ "mouse" relevance: 0.18
"it" ↔ "chased" relevance: 0.05
"it" ↔ "ran" relevance: 0.03
"it" ↔ "fast" relevance: 0.02This is reminiscent of how event bubbling and delegation work in the DOM — when an event fires, it can "see" all related parent elements in the tree. But Transformer attention is bidirectional and weighted:
If you've read any LLM technical article, you've definitely come across three letters: Q, K, V.
A great analogy is a search system:
Q (Query): "What am I looking for?" — your search term
K (Key): "What content do I have?" — each record's title/summary
V (Value): "What do I actually return?" — the content matched and returnedIn self-attention:
Each token generates its own Q, K, V (by multiplying with three weight matrices)
Token "it" wants to know what it refers to:
"it" → generates Q_it (query: "who's semantically most related to me?")
"cat" → generates K_cat (index: "I am a potential referent")
"mouse" → generates K_mouse
"chased" → generates K_chased
...
Q_it dot-product with each K → relevance scores →
softmax normalization → attention weights →
Weighted sum of all V → this is "it"'s updated meaning
(now infused with "cat" semantics, since "it" found "cat" most relevant)This mechanism can be analogized to CSS specificity calculation:
Q: I want to style <p class="intro"> (what are you querying?)
K: All CSS rule selectors (which rules declared themselves?)
V: The style declarations (what to apply when matched?)
.inner p → specificity: 0 1 1
p.intro → specificity: 0 1 1
div > p → specificity: 0 0 2The rule with highest specificity "wins" control over that element's styling — just like the most relevant token in self-attention "wins" the largest weight.
Single-head attention only sees text relationships from one perspective. Multi-head attention tackles it from several angles simultaneously:
Head 1 (syntax): "it" → looks at subject position → "cat"
Head 2 (semantics): "it" → looks at animate objects → "cat" / "mouse"
Head 3 (coreference): "it" → looks at preceding nouns → "cat"
Head 4 (sentiment): "ran very fast" → looks at sentiment words → positive
...Combining the results from all heads gives the model a multi-level, multi-faceted understanding of the text.
It's like a code review that checks multiple dimensions:
Each dimension is examined independently, and all feedback is synthesized into a complete review.
Now let's assemble these components into a Transformer layer:
Input token sequence
│
▼
┌─────────────────────────┐
│ Embedding layer │ ← Convert token IDs to vectors (numeric representation)
│ + Positional encoding │ ← Add position info ("I" at position 1, "love" at position 2)
└─────────────────────────┘
│
▼ (repeat N layers; GPT-3 has 96)
┌─────────────────────────┐
│ Multi-head self-attention│ ← Every word "sees" every other word, builds relationships
│ + Residual connection & │ ← Prevents information loss (like git merge preserving originals)
│ layer norm │
└─────────────────────────┘
│
▼
┌─────────────────────────┐
│ Feed-forward network │ ← Apply non-linear transformations to each word's representation
│ + Residual connection & │
│ layer norm │
└─────────────────────────┘
│
▼
Output → probability distribution → sample → next tokenResidual connections are critical in Transformers. Their role is like Git branching:
Main branch (input) → feature branch (attention layer applies transformation) → merge back to main (residual connection)Without residual connections, information would degrade or vanish after passing through dozens of layers. The residual connection ensures each layer enhances the original information rather than replacing it.
Modern LLM training typically goes through three stages. We'll skip the math and use analogies:
Stage 1: Pre-training
"Make the model read the entire internet"
Like showing a baby billions of pages — no right or wrong, just "seeing"
Objective: predict the next token (self-supervised learning)
Result: the model gains broad language ability and world knowledge
Stage 2: Supervised Fine-Tuning (SFT)
"Teach the model how to do Q&A"
Human-labeled high-quality Q&A pairs → model learns the "conversation format"
Objective: when given a question, output a plausible answer
Result: transforms from a "text continuer" into a "conversational assistant"
Stage 3: Reinforcement Learning from Human Feedback (RLHF)
"Teach the model which answers are better"
Humans rate responses → train a "reward model"
The reward model automatically scores LLM outputs → LLM optimizes toward higher scores
Result: responses become more helpful, safer, and better aligned with human preferencesIn front-end development terms:
Pre-training = Reading the source code of every open-source project on GitHub (builds broad knowledge)
SFT = Writing a few project templates following company coding standards (learns specific formats)
RLHF = Getting roasted in code review repeatedly until you learn what not to do (aligns with human preferences)This is actually a brilliantly elegant design. Here's why:
Open the OpenAI Tokenizer and paste in some mixed Chinese-English text. Observe:
Go to the ChatGPT Playground (requires an API key) and test the same prompt with different temperatures:
Temperature = 0, repeat 5 times — are the answers identical?Temperature = 1.5, repeat 5 times — how much do the answers diverge?Open ChatGPT and type this sentence. Watch how it continues:
Once upon a time, there was a mountain, and on the mountain there was a temple, and in the temple there wasThe LLM will naturally continue with "an old monk." Do you think it "knows the story," or is it just doing probability prediction?
Try Transformers.js in a front-end project — it runs small language models directly in the browser:
npm install @xenova/transformersimport { pipeline } from '@xenova/transformers';
// Run text generation in the browser!
const generator = await pipeline('text-generation', 'Xenova/distilgpt2');
const output = await generator('Once upon a time,', {
temperature: 0.7,
max_new_tokens: 50,
});
console.log(output[0].generated_text);See how long your browser takes to "think" about the next word.
This article is about the mental model of LLMs — helping you understand what they're doing and why. If you want to dive into the mathematical details of Transformers, check out:
If you want to integrate LLMs into front-end projects, start with these two libraries: