All articles

This LLM has a Context window of 1

Exploring the basics of LLMs by training a N-Gram model on my lecture notes

9 min read 3/3 nerds

100% Human-written

A generated sentence where only the last word is lit, above a word graph whose weighted arrows give the probabilities of the next word

Introduction

An n-gram model analyzes training data to determine how frequently specific word sequences occur. For example, given the phrase “my house is,” the model examines all words that follow “my” in the dataset. If “house” is the most common continuation, the model predicts “house.”

In this Post, I will cover the mathematical basics of modern LMs and train my own N-Gram Model based on my lecture notes. I’ll take a mathematical dive into different token selection functions and prove why the maximum likelihood assumption leads to periodic repetition.

Mathematical Basics

This section is largely based on the book Speech and Language Processing by Jurafsky & Martin (2026, I.3)

Most language models use context consisting of n−1n-1 tokens C:=w1w2⋯wn−1C:= w_1w_2\cdots w_{n-1} to predict the nth token wnw_n and describe the probability for every next token with P(wn∣w1:n−1)=P(wn∣C)P(w_n|w_{1:n-1}) = P(w_n|C). N-Gram models limit this context by assuming ≈P(wn∣wn−N+1⋯wn−1)\approx P(w_n| w_{n-N+1} \cdots w_{n-1}). In the extreme case of a bigram model (N=2N=2) this assumption simplifies to P(wn∣C)≈P(wn∣wn−1)P(w_n|C)\approx P(w_n|w_{n-1}), we call this the markov assumption. The probability of outputting a sentence s=w1w2…wms=w_{1}w_{2}\dots w_{m} with token-length mm based on context token cc is P(s)=∏w∈sP(w)P(s)=\prod_{w\in s}P(w). I define SNS_{N} as the set of all sentences with length N. And swns_{w_n} We define T(a∣B)T(a | B) as the number of occurrences of aa with the sentence BB preceeding. The next token selection is decided by Sel:C⟶wnSel: C \longrightarrow w_n.

In our case, we define P(wn∣C)=T(wn∣wn−N+1⋯wn−1)T(wn)P(w_{n}|C)=\frac{ T(w_{n}| w_{n-N+1 \cdots w_{n-1}})}{ T(w_{n})}, simplifying to P(wn∣C)=T(wn∣wn−1)T(wn)P(w_{n}|C) = \frac{T(w_n|w_{n-1})}{T(w_n)} with bigrams.

Greedy decoding always ends in a loop

Let’s take the maximum likelihood assumption of picking with Sel(C):=max⁡wi(P(wi∣C))Sel(C) := \max_{w_{i}} (P(w_{i}|C)).

Before we improve SelSel, let’s prove why choosing greedily will fail.

Proof

Adapted from Knuth (1997)

We define a greedy SelSel as:

f:V⟶V,f(w):=arg max⁡w′∈VP(w′∣w),f: V \longrightarrow V, \quad f(w) := \operatorname*{arg\,max}_{w' \in V} P(w' \mid w),
Token selection function under the maximum likelihood assumption

Given vocabulary VV with ∣V∣=v<∞|V| = v < \infty: Generation from a seed w0w_{0} is then nothing but iterating: wt=f(wt−1)=ft(w0).w_{t} = f(w_{t-1}) = f^{t}(w_{0}).

Theorem. For every w0∈Vw_{0} \in V the greedy sequence (wt)t≥0(w_{t})_{t \ge 0} is eventually periodic: there are i,j∈Ri,j \in \mathbb{R} where i<j≤vi < j \le v such that wi=wjw_{i} = w_{j}, and the sequence repeats with an arbitrary period pp: fp+1(wi)=wjf^{p+1}(w_{i})=w_j

Proof. Look at the first v+1v+1 tokens w0,w1,…,wvw_{0}, w_{1}, \dots, w_{v}. They are v+1v+1 elements drawn from a set of only vv tokens, so by the pigeonhole principle two of them must coincide: wi=wjw_{i} = w_{j} for some 0≤i<j≤v0 \le i < j \le v. Because ff is a function:

∀k≥0:wi+k=fk(wi)=fk(wj)=wj+k\forall k \ge 0:\quad w_{i+k} = f^{k}(w_{i}) = f^{k}(w_{j}) = w_{j+k}

So the sequence is periodic with period p=j−ip = j - i from index ii on. ■\blacksquare

What do we learn from that?

  1. Repetition is structural, not a bug of the dataset. Any finitely large dataset doesn’t fix the underlying problem.
  2. The model can produce at most ∣V∣|V| distinct texts
  3. Termination is luck. We can’t guarantee the loop contains a EOS.

Markov

To prevent this, we sample from P(⋅∣C)P(\cdot|C). Thus, ff is no longer a function, but a stochastic kernel of a markov chain. This leads to the key insight that a bigram model with this SelSel is equivalent to a markov chain, where nodes are tokens and edge weights are P(wn∣wn−1)P(w_n|w_{n-1}).

We can also model higher-order N-Grams as markov chains, where the number of nodes increases exponentially with VNV^N.

The better selection Funct

This section is based on Jurafsky & Martin (2026) too

The maximum likelihood assumption fails in the real world. No matter how big the training set, some sentences will never be able to be generated. That leaves the model locked on a subset ⊂\subset of real language.

One option is Laplace Smoothing:

PLaplace(wi)=T(wi+1T(wn)P_\text{Laplace}(w_{i})=\frac{ T(w_{i} + 1}{ T(w_{n})}
Laplace smoothing: Just adding 1 for every token

This leads to this Prediction function in bigram models:

PLaplace(wn∣wn−1)=T(wn−1wn)+1∑w(T(wn−1w)+1)=∑1=VT(wn−1wn)+1T(wn−1)+VP_\text{Laplace} (w_{n}|w_{n-1})=\frac{T(w_{n-1}w_n)+1}{\sum\limits_{w}(T(w_{n-1}w)+1)}\overset{\sum1=V}{=}\frac{T(w_{n-1}w_{n})+1}{T(w_{n-1})+V}
Jurafsky & Martin (2026), eq. 3.26

Laplace charges a proportional tax on evidence

What count would have produced this probability without smoothing? Solving PLaplace=T∗T(wn−1)P_\text{Laplace}=\frac{T^{*}}{T(w_{n-1})} for T∗T^{*} gives the effective count

T∗(wn−1wn)=(T(wn−1wn)+1)⋅T(wn−1)T(wn−1)+V⏟=:d(wn−1)T^{*}(w_{n-1}w_{n}) = \bigl(T(w_{n-1}w_{n})+1\bigr)\cdot\underbrace{\frac{T(w_{n-1})}{T(w_{n-1})+V}}_{=:d(w_{n-1})}
The discount factor dd does not depend on the bigram at all, only on the context. So Laplace is a proportional tax: every continuation of wn−1w_{n-1} is scaled by the same dd, no matter whether we saw it 500 times or never.

That factor is brutal at the vocabulary sizes we are dealing with. My vault has V≈2⋅104V\approx 2\cdot10^{4} types, and a decently frequent context like Gradient might have T(wn−1)=200T(w_{n-1})=200. Then d≈20020200≈0.0099d \approx \frac{200}{20200}\approx 0.0099, and the bigram Gradient Descent, observed 50 times, ends up with an effective count of roughly 0.50.5. We spent a whole corpus collecting that evidence and then threw away 99%99\% of it to fund VV continuations that never happened.

Lets add more!

Add-kk smoothing, Padd-k(wn∣wn−1)=T(wn−1wn)+kT(wn−1)+kVP_{\text{add-}k}(w_{n}|w_{n-1})=\frac{T(w_{n-1}w_{n})+k}{T(w_{n-1})+kV}, only changes the tax rate. The structural problem survives: the amount of mass taken from observed events scales with VV and with nothing about the context except its total count. A single kk tuned on held-out data is always a compromise between contexts we know a lot about and contexts we saw three times.

Discount, don’t rescale

Both scale multiplicatively. But held-out counts show the MLE error is additive: bigrams seen rr times recur about r−0.75r-0.75 times (2026). So subtract a fixed δ\delta instead. Normalization then forces the freed mass to be

λwn−1=δ N1+(wn−1∙)T(wn−1),N1+(wn−1∙)=∣{w:T(wn−1w)>0}∣\lambda_{w_{n-1}}=\frac{\delta\,N_{1+}(w_{n-1}\bullet)}{T(w_{n-1})},\qquad N_{1+}(w_{n-1}\bullet)=\bigl|\{w: T(w_{n-1}w)>0\}\bigr|

so diverse contexts leak more. It is only consulted in unseen contexts, so count how many distinct contexts ww appears in, not how often. Descent is frequent, yet almost always follows Gradient.

Knesner-Ney

PKneserNey(wi∣wi−1)=max⁡(T(wi−1wi)−δ,0)∑wT(wi−1w)⏟measured evidence, deficient by 1−λwi−1+λwi−1PKN(wi)P_{\text{KneserNey}}(w_{i}|w_{i-1})=\underbrace{\frac{\max (T(w_{i-1}w_{i})-\delta,0)}{\sum_{w}T(w_{i-1}w)}}_{\text{measured evidence, deficient by}\ 1-\lambda_{w_{i-1}}} +\lambda_{w_{i-1}}P_{KN}(w_{i})
Bigram KneserNey-Smoothing. Adapted from Ney et al. (1994)

We arrive at Knesner-Ney. The right term spends exactly the deficit the left one creates. λ\lambda is normalized, so PKNP_{KN} is freely chosen.

Perplexity

For LM evaluation, we use Perplexity. It is the exponentiated average negative log-likelihood the model assigns to held-out text:

PP(w1:M)=exp⁡(−1M∑i=1Mlog⁡P(wi∣wi−N+1:i−1))\text{PP}(w_{1:M}) = \exp\left(-\frac{1}{M}\sum_{i=1}^{M}\log P(w_{i}\mid w_{i-N+1:i-1})\right)
Perplexity: the model’s average surprise per token

Read it as an effective branching factor: how many words is the model effectively choosing between at each step? A uniform model over my vocabulary has a perplexity of exactly 15,622, because it is choosing uniformly between 15,622 options. Lower is better (Jurafsky & Martin, 2026).

Building an N-Gram Model

I was able to extract 585 .md files, skipping the following:

forbidden = [
    r"*/.claude/*",
    r"*/.obsidian/*",
    r"*.excalidraw.md",
    r"*/node_modules/*",
]

Preprocessing: Markdown is not prose, since markup is far more repetitive than text, so a model trained on the raw vault would spend most of its probability mass learning that [ follows ]. Twenty-one ordered regex substitutions normalise this, anything carrying display text is reduced to it (aliased wikilinks, markdown links, callout headers), anything else is deleted rather than replaced (frontmatter, code, math, embeds, URLs, HTML, hashtags, rules). Pure markers (headings, bullets, blockquotes, checkboxes, emphasis) are stripped. Because a lecture note is a stack of grammatically unrelated bullet fragments, each line becomes its own training sequence. All exact duplicate notes are dropped by content hash, since the same content being in training and validation set would defeat the point.

markdown files588
after deduplication533
non-empty documents388
training tokens277,208
held-out tokens11,023
types → vocabulary23,988 → 15,622 (96.98% coverage)

277k tokens is very small. For scale, GPT-3 was trained on roughly 300 billion.

How much context is worth paying for?

So I trained one model per order, from N=0N=0 (uniform) up to N=19N=19, and measured each on the same held-out documents:

N0123456810121519
Perplexity15622801137.842.628.825.524.022.822.522.523.023.9

Improvement seems to be lorarithmic. The “context window of 1” divides perplexity by almost six, and the trigram cuts it by another two thirds. After N=4N=4 perplexity doesn’t seem to improve.

Later it surprisingly goes back up. Past N=12N=12, more context makes the model measurably worse. This seemingly is because it has fewer training data for longer lines. At N=3N=3, 15% of held-out tokens arrive with a context the model has never seen; by N=12N=12 it is 35%.

What it actually writes

Perplexity doesn’t paint the whole Picture. So a test run: same seed, same prompt, one model per order:

--- N = 1 ---
the main
 of
: ,
:
.
 . 1
 the 3 the , b
die

--- N = 2 ---
the main process
:
for all
x lecture 03 .
removed and the
in order to a basis of b ,
fixed a 4 , then there is true .

--- N = 3 ---
the main process before processes are launched when using a kernel , the
removed property from the other files : , email 42 , concat name , :
the trainer no longer randomly selects a seed no longer returns a triplet 18390
: there is a subset of examples for pytorch 2.4 .
if is even .

--- N = 5 ---
the main academic risks are : unclosed gaps in securityprivacy weeks 14 15 , the rsa calculation error , and the missing .
you can decompress drawing data with the command palette : decompress current excalidraw file .

--- N = 10 ---
the main academic risks are : unclosed gaps in securityprivacy weeks 14 15 , the rsa calculation error , and the missing buffer-overflow payload practice .
a graph is planar if it can be drawn on a paper such that no edges cross each other

Every line break is the model emitting </s> and starting a fresh sequence, so the number of lines is itself a signal: the higher the order, the longer it keeps going before it decides EOS.

N=1N=1 is function words and punctuation — the model has no context, so it produces the most common tokens in the language, which are the ones carrying no meaning. N=3N=3 is the interesting one: locally grammatical, globally nonsense. “the main process before processes are launched when using a kernel” actually quite good, you’d have to secondguess to figure out it is nonsense.

And then N=5N=5 and N=10N=10 stop writing and start quoting. “A graph is planar if it can be drawn on a paper such that no edges cross each other” is a line from my notes on discrete modelling, reproduced word for word.

The minimum is a lie

That is just an impression though, so let’s measure it. For each order I sampled a few hundred lines, cut them into 8-grams, and checked how many of those 8-grams appear word for word in the training split and, separately, how many of the generated lines are entire lines of the vault reproduced exactly:

A chart visualising Citations for different N
A chart visualising Perplexity for different N

The rows move together. Every point of perplexity I bought past N=3N=3 was paid for in originality: at the perplexity minimum, two out of three 8-word spans the model produces are copied straight out of my notes, and half of the lines it writes are completely out of my notes.

At N=10N=10, 62% of the contexts the model knows occur exactly once in the entire corpus. The model isn’t choosing. It is looking up the rest of the line.

Which brings us back to its own proof. Greedy decoding fails because it is a function on a finite set, so it must eventually cycle. Sampling fixes that, but at high NN it stops repeating itself and starts repeating me. Both are a model with nothing of its own to say, and only the first one is visible in the loss.

If you want the most original model, I suggest N=3N=3 or 44: still recombining, just before the counts get sparse enough to overfit. → The best-scoring model and the best model are not the same.

One model, two languages

My notes are bilingual, and both go into one model with one vocabulary. So it code-switches mid-line, as I do in my notes:

fixed an issue to avoid validation loop : : set aller möglichen hypothesen nach der hypothesis
der lokale daemon muss als eigenständige for any

Improvement opportunities

  • Estimate the discount instead of guessing it. δ\delta is hard-coded to 0.75 throughout. Let’s change that and use a separate delta for N=1,2,3+: δ=n1n1+2n2\delta = \frac{n_1}{n_1 + 2n_2} (2026).
  • Top-k sampling. We could store the k most next wiw_i and P(wi∣C)P(w_i| C), normalized such that ∑i=1kP(wi∣C)=1\sum\limits_{i=1}^{k}P(w_{i}|C)=1 in a key-value store and run the Markov kernel only on those fixed number of tokens. This would mean Inference goes from Θ(N⋅V)\Theta(N\cdot V) per token to Θ(1)\Theta(1), independent of vocabulary and order. Note that this isn’t a lossless operation (Fan et al., 2018).

Limitation

There is a ceiling, and it is not an engineering problem. An n-gram model stores a table indexed by context, so its parameter count grows as VNV^{N}. With my modest 15,622-token vocabulary, a 10-gram table has 1562210≈104215622^{10} \approx 10^{42} cells. I observed 277,208 of them. Every additional token of context multiplies the space by 15,622 and adds zero new evidence.

That is the wall the whole field hit, and the reason the next idea had to be structurally different rather than merely bigger. This ceiling was already visible in the smoothing literature by 2001 (Goodman, 2001), and still standing in 2007 when Google trained 5-grams on two trillion tokens of web text and watched the gains go logarithmic.

Conclusion

So: what does a context window of 1 get you? →\rightarrow A perplexity of 137.8, sentences that are grammatical for about four words at a time, and a model small enough an Arduino would be able to run it easily.

It doesn’t get you anywhere near the level of competence modern LLMs have and finds no relation between similar tokens. You can’t embed tokens or find any semantically valuable information in the models.

References

  1. Fan, A., Lewis, M., & Dauphin, Y. (2018). Hierarchical Neural Story Generation. https://arxiv.org/abs/1805.04833
  2. Goodman, J. T. (2001). A bit of progress in language modeling. Computer Speech & Language, 15(4), 403–434. https://doi.org/10.1006/csla.2001.0174
  3. Jurafsky, D., & Martin, J. H. (2026). Speech and Language Processing: An Introduction to Natural Language Processing, Computational Linguistics, and Speech Recognition, with Language Models (3rd ed., I.3). https://web.stanford.edu/~jurafsky/slp3/
  4. Knuth, D. E. (1997). The Art of Computer Programming, Volume 2: Seminumerical Algorithms (3rd ed.). Addison-Wesley.
  5. Ney, H., Essen, U., & Kneser, R. (1994). On structuring probabilistic dependences in stochastic language modelling. Computer Speech & Language, 8(1), 1–38. https://doi.org/https://doi.org/10.1006/csla.1994.1001