Wednesday, August 19, 2026

Attention Is All You Need, Explained Simply

We published a plain-language walkthrough of the 2017 transformer paper — queries, keys, values, multi-head attention, and why no-recurrence mattered — with a companion animated explainer.

Tuesday, August 4, 2026

How Virtual Memory Works — LearningTechBasics

LT LearningTechBasics @amtocbot

How Virtual Memory Works

Why every program thinks it owns all of RAM.

๐Ÿ“… 2026-08-04⏱️ ~6 min read๐Ÿท️ Systems · OS

Every process runs in its own private address space, as if it had the whole machine to itself. The OS and CPU maintain that illusion by mapping virtual addresses to physical ones, page by page.

Legend — how to read this diagram

A–EComponentsthe parts involved, labelled in the diagram
1 2 3Walkthroughnumbered steps below run in order

Translating an address

  1. Virtual address. Your program uses addresses that mean nothing to the hardware directly.
  2. Page table. The OS keeps a map from virtual pages to physical frames.
  3. TLB. A small cache of recent translations avoids walking the table every time.
  4. Page fault. If a page isn't in RAM, the OS loads it from disk and retries.

What the illusion buys you

Isolation. One process can't read another's memory — different maps.

Overcommit. Programs can use more address space than physical RAM, backed by disk.

Sharing. Read-only pages (like libraries) map into many processes once.

One-line mental model:

Give each program a fake, private map of memory — and the OS quietly translates it to the real thing.

Found this useful? Three ways to go deeper — start free.
Free · Weekly One tech idea, in your inbox

The same clear explainers, delivered weekly. No spam, unsubscribe anytime.

Subscribe free →
Guide · $39 The Open-Source AI Stack

120+ page production guide: local LLMs, fine-tuning, RAG, and domain-specific AI.

Get the guide →
Consulting Need this built properly?

AI implementation, fine-tuning and RAG done right. Free 30-minute strategy call.

Book a call →

Thursday, July 30, 2026

How Race Conditions Happen — LearningTechBasics

LT LearningTechBasics @amtocbot

How Race Conditions Happen

Two threads, one variable, and a bug that only shows up sometimes.

๐Ÿ“… 2026-07-30⏱️ ~5 min read๐Ÿท️ Concurrency · Systems

A race condition is a bug where the result depends on the exact timing of concurrent operations. It hides in the gap between reading a value and writing it back.

Legend — how to read this diagram

A · BPartiesthe two sides of the exchange
1–nOrdereach message, numbered in sequence
1 2 3Walkthroughnumbered steps below run in order

The classic lost update

  1. Both read. Thread A and Thread B both read count = 5.
  2. Both compute. Each independently decides the new value is 6.
  3. Both write. They both store 6 — but two increments happened, so it should be 7.
  4. Non-determinism. Whether it breaks depends on scheduling, so it passes tests and fails in production.

How to prevent them

Locks. A mutex makes read-modify-write atomic, one thread at a time.

Atomics. Hardware atomic operations do increment as a single indivisible step.

Avoid shared state. Message passing and immutability sidestep the problem entirely.

One-line mental model:

When correctness depends on who wins a timing race, you don't have a program — you have a coin flip.

How CDNs Work — LearningTechBasics

LT LearningTechBasics @amtocbot

How CDNs Work

Why a site loads fast whether you're in Tokyo or Toronto.

๐Ÿ“… 2026-07-30⏱️ ~5 min read๐Ÿท️ Networking · Performance

A Content Delivery Network puts copies of your content in hundreds of locations worldwide, so users are served from a nearby edge instead of your single origin server far away.

Legend — how to read this diagram

A–DComponentsthe parts involved, labelled in the diagram
Requestdata travelling outward
Responsedata returning
1 2 3Walkthroughnumbered steps below run in order

How a request is served

  1. Anycast routing. The user's request goes to the nearest edge point of presence automatically.
  2. Cache hit. If the edge already has the file, it returns it immediately.
  3. Cache miss. Otherwise the edge fetches from origin, stores it, and serves it.
  4. TTL & purge. Cached copies expire on a TTL or can be purged when content changes.

Beyond speed

Offload. The origin handles a fraction of traffic, saving cost and load.

Resilience. Edges absorb spikes and shield the origin from DDoS.

Dynamic too. Modern CDNs run code at the edge, not just cache static files.

One-line mental model:

Move the content close to the user, and distance stops being the bottleneck.

Wednesday, July 29, 2026

How Compilers Work — LearningTechBasics

LT LearningTechBasics @amtocbot

How Compilers Work

From text you wrote to instructions a CPU runs.

๐Ÿ“… 2026-07-29⏱️ ~6 min read๐Ÿท️ Languages · Systems

A compiler is a translator with several passes. It reads your source, checks it makes sense, and lowers it step by step into machine code — optimizing along the way.

Legend — how to read this diagram

1–nStagesthe ordered steps of the process
1 2 3Walkthroughnumbered steps below run in order

The classic phases

  1. Lexing. Break the source into tokens: keywords, identifiers, literals.
  2. Parsing. Assemble tokens into an abstract syntax tree following the grammar.
  3. Semantic analysis. Type-check, resolve names, catch misuse.
  4. IR. Lower the tree into a simpler intermediate representation.
  5. Optimize. Fold constants, inline, remove dead code on the IR.
  6. Codegen. Emit machine instructions for the target CPU.

Why the middle exists

Separation. A shared IR lets one backend serve many languages and one language target many CPUs.

Optimization surface. The IR is where most speedups happen, independent of syntax.

JIT vs AOT. Some compile ahead of time; others compile hot paths while the program runs.

One-line mental model:

Compiling is a staircase: each pass lowers your code to something simpler until only machine instructions remain.

Tuesday, July 28, 2026

How OAuth Works — LearningTechBasics

LT LearningTechBasics @amtocbot

How OAuth Works

"Log in with Google" — without Google ever seeing the other site's password.

๐Ÿ“… 2026-07-28⏱️ ~6 min read๐Ÿท️ Security · WebDev

OAuth lets one app act on your behalf at another service without ever handling your password. Instead of credentials, apps get a scoped, revocable token.

Legend — how to read this diagram

A · BPartiesthe two sides of the exchange
1–nOrdereach message, numbered in sequence
1 2 3Walkthroughnumbered steps below run in order

The authorization-code flow

  1. Redirect. The app sends you to the provider with the scopes it wants.
  2. Consent. You authenticate with the provider and approve (or deny) those scopes.
  3. Code. The provider redirects back to the app with a short-lived authorization code.
  4. Token exchange. The app's server swaps the code (plus its secret) for an access token.
  5. Use & refresh. The app calls APIs with the token, refreshing it as needed.

Why it's safer than sharing a password

Scoped. A token grants only the permissions you approved, not full account access.

Revocable. You can revoke one app without changing your password.

PKCE. Public clients add a proof step so an intercepted code alone is useless.

One-line mental model:

Hand out a narrow, revocable token — never the password itself.

How LLMs Generate Text — LearningTechBasics

LT LearningTechBasics @amtocbot

How LLMs Generate Text

One token at a time — a very well-read autocomplete.

๐Ÿ“… 2026-07-28⏱️ ~6 min read๐Ÿท️ AI · Machine Learning

A large language model doesn't plan a whole answer up front. It predicts the next token from everything so far, appends it, and repeats — with attention letting it weigh which earlier words matter most.

Legend — how to read this diagram

1–nStagesthe ordered steps of the process
1 2 3Walkthroughnumbered steps below run in order

How each token appears

  1. Tokenize. Text is split into subword tokens and mapped to numbers.
  2. Embed. Each token becomes a vector encoding meaning and position.
  3. Attention. Every token looks at the others and decides what to focus on.
  4. Predict. The model outputs a probability for every possible next token.
  5. Sample. Temperature and top-p pick one; append it and feed the whole thing back in.

Why it feels coherent

Context window. The model sees thousands of prior tokens at once, keeping track of the thread.

Scale of training. Patterns from vast text let it continue in-style and on-topic.

It's still prediction. No lookup of facts — which is why it can sound confident yet be wrong.

One-line mental model:

Generation is autocomplete with attention: predict the next token, append, repeat.

Monday, July 27, 2026

How Neural Networks Learn — LearningTechBasics

LT LearningTechBasics @amtocbot

How Neural Networks Learn

Guess, measure the error, nudge every weight — a few million times.

๐Ÿ“… 2026-07-27⏱️ ~6 min read๐Ÿท️ AI · Machine Learning

A neural network starts out knowing nothing — its weights are random. Learning is a loop: make a prediction, measure how wrong it was, and shift every weight slightly in the direction that reduces the error.

Legend — how to read this diagram

1–nStagesthe ordered steps of the process
1 2 3Walkthroughnumbered steps below run in order

One training step

  1. Forward pass. Inputs flow through layers of weighted sums and nonlinear activations to a prediction.
  2. Loss. A loss function scores how far the prediction is from the truth.
  3. Backprop. The chain rule computes how much each weight contributed to the error.
  4. Update. Gradient descent nudges each weight opposite its gradient by a small learning rate.
  5. Repeat. Over many batches, the loss drops and the network generalizes.

Why it generalizes (usually)

Nonlinearity. Activations let stacked layers approximate complex functions.

Regularization. Dropout and weight decay stop it from memorizing the training set.

Data & scale. More diverse data and parameters generally mean better generalization — up to a point.

One-line mental model:

Learning is just error, blamed correctly across millions of knobs, then each knob turned a little.

Context Compression in Self-Hosted RAG: Fitting More Signal Into the Context Window

Hero image showing retrieved chunks being compressed before entering the context window

I was retrieving K=10 chunks for every query and watching the model's answer quality plateau. Increasing K to 20 didn't help, and it made latency and token cost worse. The problem wasn't that I needed more retrieved content. The problem was that most of what I retrieved wasn't relevant to the specific question.

Context compression is the step between retrieval and generation: take the retrieved chunks, extract only the parts that directly address the query, and pass a shorter, denser context to the model. The same context window fits more signal and less noise.

This post covers how to implement context compression in a self-hosted pipeline, what compressors work at production latency, and how to measure whether compression is improving answer quality.

Why Retrieved Chunks Are Noisy

Retrieval operates at the chunk level. Each chunk was split at a fixed boundary: by token count, by paragraph, or by heading. The boundary has no knowledge of future queries. When a user asks a narrow question, the relevant sentences may be scattered across several chunks, each of which also contains unrelated content.

Three types of noise in retrieved chunks:

Structural noise: headers, footers, navigation text, repeated boilerplate. These score well on embeddings because they appear near relevant content, but add nothing to the answer.

Topical noise: a chunk about topic A that also mentions topic B. The query is about topic B, so the chunk retrieves, but half its tokens are about topic A.

Redundancy: multiple chunks that say the same thing in slightly different words. Reranking helps, but rarely eliminates all redundancy.

Passing all of this to the model wastes tokens and dilutes the signal-to-noise ratio in the prompt.

Compression Approaches

Approach 1: Extractive Compression

Extract only the sentences from each chunk that are relevant to the query. No summarization: the compressed output is a verbatim subset of the original.

import ollama

EXTRACTIVE_PROMPT = """Given the following retrieved passage and a user query, extract only the sentences from the passage that directly help answer the query. Return only the extracted sentences, preserving their original wording. If no sentences are relevant, return an empty string.

Query: {query}

Passage:
{passage}

Extracted sentences:"""

def extractive_compress(query: str, passage: str, model: str = "llama3.2:3b") -> str:
    response = ollama.generate(
        model=model,
        prompt=EXTRACTIVE_PROMPT.format(query=query, passage=passage),
        options={"temperature": 0}
    )
    return response["response"].strip()

Extractive compression is fast with a small model (llama3.2:3b adds a few hundred milliseconds per chunk in our tests) and preserves exact wording, which matters for factual queries where paraphrasing introduces error.

Approach 2: Abstractive Compression

Summarize each chunk in the context of the query. The output is shorter than extractive but may rephrase content.

ABSTRACTIVE_PROMPT = """Summarize the following passage to include only information relevant to answering the query. Be concise. If the passage contains nothing relevant, return an empty string.

Query: {query}

Passage:
{passage}

Summary:"""

def abstractive_compress(query: str, passage: str, model: str = "llama3.2:3b") -> str:
    response = ollama.generate(
        model=model,
        prompt=ABSTRACTIVE_PROMPT.format(query=query, passage=passage),
        options={"temperature": 0}
    )
    return response["response"].strip()

Abstractive compression produces more compact output but introduces a small risk of hallucination in the compression step itself. Use it for conceptual questions where paraphrase is acceptable, and extractive for factual lookups.

Approach 3: Sentence-Level Filtering (No LLM)

Score each sentence in a chunk by cosine similarity to the query embedding. Keep only sentences above a threshold. Fast, no LLM call, but misses cross-sentence context.

import ollama
import numpy as np

def embed(text: str) -> np.ndarray:
    vec = ollama.embeddings(model="nomic-embed-text", prompt=text)["embedding"]
    return np.array(vec)

def sentence_filter_compress(
    query: str,
    passage: str,
    threshold: float = 0.6
) -> str:
    query_vec = embed(query)
    sentences = [s.strip() for s in passage.split('.') if s.strip()]
    kept = []
    for sentence in sentences:
        if len(sentence) < 10:
            continue
        sent_vec = embed(sentence)
        score = float(np.dot(query_vec, sent_vec) / (
            np.linalg.norm(query_vec) * np.linalg.norm(sent_vec)
        ))
        if score >= threshold:
            kept.append(sentence)
    return '. '.join(kept) + ('.' if kept else '')

Sentence filtering adds an embedding call per sentence (fast) instead of an LLM generation call. At K=10 chunks with an average of 8 sentences each, that's 80 embedding calls, which completes in well under a second with nomic-embed-text on a CPU in our tests.

Full Pipeline with Compression

def rag_with_compression(
    query: str,
    k: int = 10,
    compression: str = "extractive",  # "extractive", "abstractive", "sentence"
    min_compressed_length: int = 20
) -> dict:
    # Retrieve
    chunks = retrieve(query, k=k)

    # Compress each chunk
    compressed = []
    for chunk in chunks:
        if compression == "extractive":
            result = extractive_compress(query, chunk["text"])
        elif compression == "abstractive":
            result = abstractive_compress(query, chunk["text"])
        else:
            result = sentence_filter_compress(query, chunk["text"])

        # Drop empty or near-empty results
        if len(result.strip()) >= min_compressed_length:
            compressed.append(result)

    if not compressed:
        # Fall back to top-3 uncompressed if compression removed everything
        compressed = [chunk["text"] for chunk in chunks[:3]]

    context = "\n\n".join(compressed)
    response = generate(query, context)
    return {"response": response, "compressed_chunks": len(compressed), "original_chunks": len(chunks)}

When to Apply Compression

Compression adds latency. Apply it selectively:

Good candidates for compression:
- Long chunks (500+ tokens) where queries are narrow
- FAQ and support RAG where the query asks for a single fact buried in a larger document
- Synthesis queries where multiple chunks overlap significantly

Poor candidates for compression:
- Very short chunks (a sentence or two) where compression overhead exceeds benefit
- Chunks that are already tightly scoped to one topic
- Queries that need full procedural context (step-by-step instructions where any omitted step breaks the answer)

def should_compress(chunks: list[dict], query: str, token_threshold: int = 200) -> bool:
    avg_chunk_tokens = sum(len(c["text"].split()) for c in chunks) / len(chunks)
    return avg_chunk_tokens > token_threshold

Measuring Compression Effectiveness

Track three metrics:

Compression ratio: tokens in compressed context / tokens in original context. A ratio below 0.5 is aggressive; 0.6-0.8 is typical for extractive compression.

Answer quality: measure faithfulness (does the answer contradict the source?) and relevance (does the answer address the query?) against a labeled eval set. Use an LLM judge at each threshold.

Latency delta: compression adds time. Track whether the token savings at generation offset the compression overhead in wall-clock time.

class CompressionMetrics:
    def __init__(self):
        self.total = 0
        self.original_tokens = []
        self.compressed_tokens = []
        self.latency_compression_ms = []
        self.latency_generation_ms = []

    def record(
        self,
        original_tokens: int,
        compressed_tokens: int,
        compression_ms: float,
        generation_ms: float
    ) -> None:
        self.total += 1
        self.original_tokens.append(original_tokens)
        self.compressed_tokens.append(compressed_tokens)
        self.latency_compression_ms.append(compression_ms)
        self.latency_generation_ms.append(generation_ms)

    def report(self) -> dict:
        avg = lambda lst: sum(lst) / len(lst) if lst else 0
        return {
            "avg_compression_ratio": avg(self.compressed_tokens) / avg(self.original_tokens) if avg(self.original_tokens) else 0,
            "avg_compression_latency_ms": avg(self.latency_compression_ms),
            "avg_generation_latency_ms": avg(self.latency_generation_ms),
            "total_queries": self.total,
        }

Combining Compression with Reranking

Compression and reranking are complementary. Reranking selects the best chunks; compression extracts the best content from those chunks. A typical order:

  1. Retrieve K=20 (broad recall)
  2. Rerank to top 5 (precision)
  3. Compress each of the top 5 (reduce noise)
  4. Generate from the compressed context

This gives the model a context that is both high-precision (from reranking) and high-density (from compression).

When Context Compression Is Worth the Complexity

Compression adds a model call or embedding calls per retrieved chunk, plus code to handle empty outputs and fallbacks. It is worth it when:

  • Your chunks are large relative to your queries
  • You are hitting context window limits at K values that give acceptable recall
  • Token cost at generation is a meaningful constraint (large hosted models)

If your chunks are small and well-scoped, or your context window is large relative to K * chunk_size, compression adds complexity without meaningful benefit. Profile token usage per query before adding compression.


Get the next one

I send one short email a week: one production bug, debugged, plus the
companion code for each deep-dive. No spam, unsubscribe anytime.

๐Ÿ‘‰ Subscribe (free)

Reader challenge: are you compressing retrieved context before generation? What compressor approach works for your chunk sizes and query distribution? Reply with what you found.

Sources

  1. LangChain ContextualCompressionRetriever: https://python.langchain.com/docs/how_to/contextual_compression/
  2. nomic-embed-text on Ollama: https://ollama.com/library/nomic-embed-text
  3. llama3.2 on Ollama: https://ollama.com/library/llama3.2

About the Author

Toc Am

Founder of AmtocSoft. Writing practical deep-dives on AI engineering, cloud architecture, and developer tooling. Previously built backend systems at scale. Reviews every post published under this byline.

LinkedIn X / Twitter

Published: 2026-07-27 · Written with AI assistance, reviewed by Toc Am.

Get These In Your Inbox

Weekly deep-dives on AI engineering, no fluff. Join the newsletter →

Subscribe (free)

Or grab the book ($39, ~100 pages) · Buy me a coffee

Buy Me a Coffee · ๐Ÿ”” YouTube · ๐Ÿ’ผ LinkedIn · ๐Ÿฆ X/Twitter

Attention Is All You Need, Explained Simply

We published a plain-language walkthrough of the 2017 transformer paper — queries, keys, values, multi-head attention, and why no-recurrence...