Search Tech Journey

Find topics, journeys and posts

back to blog
mladvanced 120m read

DL S049 · Quality Filters and Deduplication in Practice

Build a quality-filter pipeline you'd actually ship. Part of the 'Deep Learning & LLMs From Scratch' 80-session self-study series.

🧠SoftwareM08 · Tokenization + data + scaling laws· Session 049 of 130 120 min

🎯 Build production-grade near-duplicate detection and quality classification for pretraining corpora.

Series: Deep Learning & LLMs From Scratch — 80 sessions · Session 49 / 80 · Module M08 · ~2 hours

The story we're starting with

In S046 we hand-waved "then we dedup" and moved on. That was a lie by omission — because at 100 GB of corpus, exact hashing catches maybe 30% of the duplication. The remaining 40–60% of "duplicated content" is near-duplicate: the same news article rewritten, syndicated across 40 sites, mirrored in RSS aggregators, or reposted with a different headline. Exact hash misses all of it. And if you skip near-dedup, your model memorizes news templates instead of learning language.

This is the session where we implement MinHash + LSH from scratch — the algorithm that lets you find "documents 80%+ similar to each other" in sub-quadratic time across a corpus of 100M+ documents. Every serious modern corpus (SlimPajama, RefinedWeb, FineWeb, DCLM) is built on MinHash-LSH. You need to understand it well enough to tune the parameters and debug when it misbehaves.

We'll also cover the newer half of quality filtering: classifier-based scoring. Traditional heuristic filters (Gopher-style, from S046) catch obvious junk. But if you want to compete with FineWeb-Edu, you need a small trained classifier that scores each doc for "educational value" or "coherent prose" and thresholds. It's ~200 lines of fastText. It reliably adds 3–5 points to downstream evals.

You will be able to
  • Explain what Jaccard similarity measures and why we can't compute it pairwise on 100M docs.
  • Implement MinHash from scratch in ~30 lines and reason about the collision probability.
  • Explain the (b, r)-banding LSH trick and pick (b, r) for a target similarity threshold.
  • Train a fastText quality classifier on labeled 'good vs junk' data in under 5 minutes.
  • Design a full quality-and-dedup pipeline stage-by-stage, with expected shrink at each step.

Prerequisites

  • S046 (Pretraining dataset) — we're adding the near-dedup and quality-classifier stages that were placeholders there.
  • S011 (MNIST in NumPy) — general Python + a bit of hashing comfort.


1 · Why exact hashing isn't enough

Toy example. Two docs:

A: "The president visited Tokyo on Tuesday to discuss trade."

B: "On Tuesday, the president visited Tokyo to discuss trade issues."

Exact hash: totally different (different bytes → different hash). Jaccard similarity on 5-gram shingles: ~0.8. To a language model, these are the same document. Train on both and you're wasting compute + risking memorization.

Now imagine the same story reprinted on 30 news sites with slightly different intros, different photo captions, and different editor byline. That's the actual scale of the problem. Lee et al. (2022) measured on C4 that ~1% of exact duplicates and another ~3% of near-duplicates existed. On raw CC pre-C4, near-dup levels are 15–40%.

Jaccard similarity between two documents:

J(A, B) = |shingles(A) ∩ shingles(B)| / |shingles(A) ∪ shingles(B)|

Where shingles(A) is the set of overlapping n-grams (usually 5-word shingles). Computed on the set, so word order sort-of matters but repetition doesn't.

Computing J pairwise for N docs is O(N²) — for 100M docs that's 10^16 comparisons. Impossible. MinHash + LSH gives you the same answer in O(N log N) with tunable false-positive rate.

Fingerprints instead of full body scans
🌍 Real world
💻 Code world

2 · MinHash — the "unbiased Jaccard estimator" trick

The claim (Broder, 1997) is magical: pick a random permutation of the universe of all possible shingles. For any doc D, let h(D) = the minimum shingle (under the permutation) that appears in D. Then Pr[h(A) = h(B)] = J(A, B) exactly.

Read that again. If two docs have Jaccard 0.7, then 70% of random permutations will assign them the same "min shingle". Which means: if I compute 128 such min-hashes per doc and count how many agree, I estimate Jaccard to ±5% accuracy — with no pairwise comparison of full documents.

2.1 The 30-line implementation

Real permutations of "all possible shingles" are infeasible, so we simulate them with hash functions. Each hash function h_i acts as an independent random permutation.

import mmh3  # MurmurHash3 — fast, good enough randomness
 
def shingles(text: str, n: int = 5) -> set[str]:
    words = text.lower().split()
    return {" ".join(words[i:i+n]) for i in range(len(words) - n + 1)}
 
def minhash(text: str, num_perm: int = 128, seed_base: int = 0) -> list[int]:
    shs = shingles(text)
    if not shs:
        return [0] * num_perm
    sigs = []
    for i in range(num_perm):
        # h_i is the i-th "random permutation"
        min_h = min(mmh3.hash(sh, seed=seed_base + i, signed=False) for sh in shs)
        sigs.append(min_h)
    return sigs

That's the whole algorithm. For each of 128 hash seeds, hash every shingle and take the minimum. The 128-vector is the doc's "signature".

2.2 Estimating Jaccard from signatures

def est_jaccard(sig_a: list[int], sig_b: list[int]) -> float:
    return sum(a == b for a, b in zip(sig_a, sig_b)) / len(sig_a)

That's it. Count matching positions, divide by length. Standard error is sqrt(J(1-J)/K); for K=128 and J=0.5 that's ±0.044.

Try itSanity-check the MinHash estimator on synthetic pairs

Generate two synthetic documents by taking a base string of 500 unique 5-grams, then create doc B by replacing X% of them with new 5-grams (so the true Jaccard is (1 - X/100) / (1 + X/100) — do the algebra). For X in [10, 30, 50, 70, 90], compute minhash(A) and minhash(B) with num_perm=128, estimate Jaccard from signatures, and compare to the true value. You should see the estimate track true J within ~0.04. Then repeat with num_perm=32 and confirm the error roughly doubles — exactly what the sqrt(J(1-J)/K) formula predicts.

💡 Hint · Vary `num_perm` from 32 to 256 and watch the error shrink.

MinHash still needs pairwise comparison of signatures — O(N²) — but each comparison is a fast integer vector compare instead of a set operation. That's 100× faster but still not enough for 100M docs. Enter LSH.


3 · LSH — banding to make the query sub-quadratic

The idea: split each 128-hash signature into b bands of r hashes each (with b · r = 128). Two docs "collide" if they match on at least one entire band. Use those collisions as candidate pairs; verify with full Jaccard estimate.

The collision probability for two docs with true Jaccard J:

P(collide) = 1 - (1 - J^r)^b

Plot this for a few (b, r) pairs. It's an S-curve. The steepest part of the S is around J* = (1/b)^(1/r) — the "similarity threshold" LSH is tuned to.

(b, r) recipes and what threshold they target

    For pretraining corpora, b=16, r=8 targeting J=0.8 is the SlimPajama/FineWeb standard.

    3.1 The 40-line LSH implementation

    from collections import defaultdict
     
    class MinHashLSH:
        def __init__(self, num_perm: int = 128, b: int = 16):
            assert num_perm % b == 0
            self.num_perm = num_perm
            self.b = b
            self.r = num_perm // b
            self.buckets = [defaultdict(list) for _ in range(b)]
            # buckets[i] = {band_hash: [doc_ids]} for band i
     
        def _band_hashes(self, sig: list[int]) -> list[int]:
            return [hash(tuple(sig[i*self.r:(i+1)*self.r])) for i in range(self.b)]
     
        def add(self, doc_id: str, sig: list[int]) -> None:
            for i, bh in enumerate(self._band_hashes(sig)):
                self.buckets[i][bh].append(doc_id)
     
        def query(self, sig: list[int]) -> set[str]:
            candidates = set()
            for i, bh in enumerate(self._band_hashes(sig)):
                candidates.update(self.buckets[i].get(bh, []))
            return candidates

    Use:

    lsh = MinHashLSH()
    sigs = {}
    for doc_id, text in corpus:
        sig = minhash(text)
        for cand in lsh.query(sig):
            if est_jaccard(sig, sigs[cand]) > 0.8:
                mark_duplicate(doc_id)
                break
        else:
            sigs[doc_id] = sig
            lsh.add(doc_id, sig)

    Cost: O(N · b) inserts, O(N · b · avg_bucket_size) queries. Avg bucket size stays small when b is well-chosen. Empirically this is 1000× faster than pairwise Jaccard on real corpora.

    3.2 Memory

    Per doc: 128 × 4-byte hashes = 512 bytes for the signature. For 100M docs = 50 GB of signatures. Plus the LSH buckets (~similar order). Doable on one 64 GB RAM box; for bigger corpora shard across machines by band-hash prefix.

    For 5M docs (our 1B-token target) it's <3 GB — fits comfortably in laptop RAM.


    4 · Trained quality classifiers — the FineWeb-Edu trick

    Heuristic filters (S046 Gopher rules) get you 80% of the way. The last 20% needs a learned filter.

    The recipe (from FineWeb-Edu, Phi-1 "textbook is all you need", DCLM baseline):

    1. Take ~10k documents from your corpus.
    2. Label them with a strong LLM as prompt: "Rate this document on educational value (0–5)."
    3. Train a small fastText supervised classifier on the labels (text -> {0,1,2,3,4,5}).
    4. Score every doc in the corpus. Threshold at some cutoff.
    5. Keep only docs above threshold.

    4.1 Why fastText and not a transformer

    • 200-line C++ underneath, ~50k docs/sec on CPU. A neural classifier is 100× slower.
    • Accuracy comparable for this task (~2% worse than a fine-tuned BERT), speed 1000× better.
    • Ships as a 500 MB binary you can distribute.

    4.2 Implementation

    # Prep labels — sample + LLM-label
    python label_docs.py --sample_size 10000 --output labels.tsv
    # labels.tsv format: __label__3\tThe rise of neural networks in medicine...
     
    # Train
    fasttext supervised -input labels.tsv -output qualifier \
        -lr 0.5 -epoch 10 -wordNgrams 2 -dim 100

    Then in Python:

    import fasttext
    model = fasttext.load_model("qualifier.bin")
     
    def is_quality(text: str, threshold: float = 3.0) -> bool:
        lbl, prob = model.predict(text[:1000].replace("\n", " "))
        score = float(lbl[0].removeprefix("__label__"))
        return score >= threshold

    Empirical gains: FineWeb-Edu (trained edu classifier at threshold 3) beat FineWeb (heuristics only) by ~4 points on MMLU at fixed compute. Phi-1 reported ~7 point HumanEval gains from classifier-filtered code.


    5 · The full pipeline, sequenced

    Order matters — cheap stages first, expensive stages last, so expensive stages see less data.

    Cost per stage on a c7i.4xlarge (16 vCPU) for 1 TB raw text:

    StageWall clockCost @ $0.72/hr
    Extraction4 h$2.90
    Lang ID1 h$0.72
    Gopher30 min$0.36
    Exact dedup45 min$0.54
    MinHash3 h$2.16
    LSH query + verify2 h$1.44
    Quality classifier1 h$0.72
    PII + tokenize + shard30 min$0.36
    Total~13 h~$9

    Nine bucks to make 1 TB raw → ~50 GB clean. That is the price for a small production-grade corpus.


    6 · Ablating dedup at scale — the money experiment

    Lee et al. (2022) trained several small models on identical corpora with and without dedup. Results (paraphrased):

    • Removing exact dupes: ~10× less memorization of specific training examples at generation time.
    • Removing near-dupes (MinHash, J>0.8): ~5–10% lower validation loss and further ~3× less memorization.
    • Combined effect: higher held-out perplexity, better downstream evals, no memorization of training data.

    This paper singlehandedly moved dedup from "nice to have" to "table stakes". You cannot ship a serious pretrained model without it.


    7 · MinHash gotchas

    Things that will bite you

      8 · War stories

      War story The MinHash 'no dupes found' surprise

      Ran MinHash on a 50M-doc corpus. Result: 0 near-duplicates. Suspicious. Turned out my shingle function was set(text.split()) — I was treating docs as bags of words, not 5-grams. Two docs that share a vocabulary but say totally different things have Jaccard ~0.7 on unigrams. My LSH bucketed everything into "everything is dup" and my threshold silently rejected all pairs.

      Lesson: always use 5-word shingles (or 3-char shingles for tokenized text). Word-bag Jaccard is a different problem.

      War story The quality-classifier that hated code

      I trained an edu-quality classifier on labeled prose. Then ran it on my full corpus. My entire code split got scored <2 (below threshold) — because code doesn't look like educational prose. I'd have accidentally filtered out my whole code corpus if I hadn't spot-checked.

      Lesson: always run the classifier on samples from each domain, not just aggregate. And ideally train a per-domain classifier or an "acceptable in domain" classifier.


      Common misconception
      ✗ What most people think

      "MinHash + LSH gives me the near-duplicate pairs. It's a similarity index — I set the threshold, it returns everything above it."

      ✓ What is actually true

      MinHash+LSH is a probabilistic candidate generator, not a threshold filter. The banding curve is an S-curve, not a step function: pairs above your threshold are usually retrieved and pairs below are usually skipped, with a soft transition in between. You get false negatives at any parameter setting, and the only way to remove false positives is to recompute exact Jaccard on the candidates LSH hands you.

      Why the myth is so sticky

      Because the demo always works. On a toy corpus with obvious duplicates — the same article twice, one with a changed byte — Jaccard is 0.99 and every band collides, so recall looks like 100%. The regime where the S-curve matters is the one you cannot eyeball: pairs at 0.75–0.85 similarity, which is exactly where "boilerplate-heavy but genuinely different documents" live. So the belief is formed in the region where the approximation is indistinguishable from exact, and applied in the region where it isn't.

      Prove it to yourself

      Plot the actual retrieval probability curve for your own (b, r) and see the softness with your own eyes:

      b, r = 20, 5   # 100 permutations, 20 bands of 5 rows
      for s in [0.5, 0.6, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95]:
          p = 1 - (1 - s**r)**b       # P(at least one band collides)
          print(f"jaccard={s:.2f}  P(candidate)={p:.3f}")
      # note: p is never 0 below your threshold and never 1 above it
      From first principles
      Start with the question

      Why does the min of a permuted hash estimate Jaccard at all? This looks like a coincidence — one number out of a whole set, and somehow it knows about set overlap.

      1. 1
        Take a uniformly random permutation π of the universe of shingles, and define h(S) = min over x in S of π(x).
        forced by · we need a summary of a set that is a single number, or signatures cannot be cheaper than the sets themselves
      2. 2
        h(A) equals h(B) exactly when the element achieving the minimum over A ∪ B happens to lie in A ∩ B.
        forced by · the min over A and the min over B are both determined by scanning A ∪ B in π-order and stopping at the first element; they agree only if that first element is in both sets
      3. 3
        Under a uniform random π, every element of A ∪ B is equally likely to be the one that comes first.
        forced by · uniformity is the whole point of the permutation — no element has a privileged position
      4. 4
        So P[h(A) = h(B)] = |A ∩ B| / |A ∪ B| = Jaccard(A, B), exactly. Not approximately — exactly, in expectation.
        forced by · the probability of "first element lands in the intersection" is just the intersection's share of the union
      5. 5
        Each of the k permutations is therefore an independent Bernoulli trial with success probability J. The fraction of matching signature rows is the sample mean of those trials.
        forced by · independent hash functions give independent trials, and a sample mean of Bernoulli trials is an unbiased estimator of p
      ⇒ Therefore

      Therefore signature agreement rate is an unbiased estimator of Jaccard, and its standard error is √(J(1−J)/k) — governed only by k, the number of permutations, and not at all by document length or corpus size.

      And note the prediction: to halve your estimation error you must quadruple k, not double it. Verify it directly — estimate the Jaccard of one known pair with k = 32, 128, and 512 permutations, repeat each 200 times, and take the standard deviation of your estimates. The three numbers should fall roughly as 1 : 1/2 : 1/4. This is also why nobody runs MinHash at k = 10000: the accuracy return is square-root, but the cost is linear.

      Mental modelTwo-stage sieve: cheap recall, then exact precision

      Picture dedup as a coarse sieve bolted in front of a fine one. The coarse stage — LSH banding — is allowed to be sloppy in one direction only: it may pass junk through, but it must not drop true duplicates, because nothing downstream can recover them. The fine stage — exact Jaccard on the small candidate list — is allowed to be expensive, because it only ever sees the survivors.

      Every knob you have is a knob on the first sieve. More rows per band (r ↑) makes the mesh finer and drops borderline pairs; more bands (b ↑) gives each pair more chances to survive and lets junk through. The second sieve never changes.

      • Signature length k buys estimation accuracy (error ∝ 1/√k). The (b, r) split of that k buys where the S-curve sits — approximate threshold ≈ (1/b)^(1/r).
      • b × r = k. You cannot raise recall and precision at the same time without raising k, i.e. without paying more compute.
      • Bias the first stage toward recall. A false positive costs one exact-Jaccard computation; a false negative costs a duplicate in your training set forever.
      • Exact hashing (dedup by document SHA) is the r = ∞ corner of the same picture: perfect precision, catastrophic recall, because one changed byte moves the hash.
      🔔 Fires when you see

      Fire this model the moment you see: a dedup pass that "found nothing" on a web crawl · a similarity search that misses obvious matches · someone tuning a single "threshold" parameter for LSH · a candidate list that blew up to O(n²) · any system built on Bloom filters, SimHash, or a vector-index recall@k number.

      The tradeoff

      How aggressively should you deduplicate a pretraining corpus — exact-match only, document-level fuzzy, or paragraph/span-level?

      Exact document hashing only
      + you gain trivially cheap, one pass, zero tuning, perfectly reproducible, and zero risk of deleting something that was not literally identical
      − you pay near-zero recall on real crawls — the same article behind different CMS wrappers, with a different nav bar or a trailing timestamp, hashes differently and survives; you end up training on the same content many times over
      pick when your corpus comes from a single curated source with no syndication or mirroring — internal documents, one publisher's archive, a single code monorepo
      Document-level MinHash/LSH fuzzy dedup
      + you gain catches the dominant duplicate class on web data (templated wrappers, syndicated news, mirrors) at roughly linear cost; parameters are interpretable and the whole thing parallelises trivially by band
      − you pay you must store k-length signatures for every document, and the shuffle to group by band key is usually the real bottleneck; borderline pairs are decided by an S-curve you chose somewhat arbitrarily
      pick when the corpus is web-derived or multi-source and large enough that duplicate content is measurable — this is the default for any crawl-based pretraining set
      Span/paragraph-level dedup (suffix-array style)
      + you gain removes repeated substrings that document-level dedup cannot see: license headers, cookie banners, boilerplate footers repeated across otherwise-distinct pages
      − you pay far more expensive to build and to run, and it mutilates documents — cutting a repeated span out of the middle of a page leaves ungrammatical seams the model then learns; also destroys the property that a training document is a real document
      pick when you can measure that a large share of your tokens sit in short high-frequency spans, and you have the budget to validate that the surgery did not degrade fluency
      What a senior engineer actually does

      Document-level fuzzy dedup is the load-bearing one; run it, and run it before your quality classifier so the classifier is not scoring the same document a hundred times. Tune (b, r) toward recall, then let exact Jaccard on the candidate set give you back the precision.

      The discipline that matters more than the parameter choice: keep the dedup decision logged and reversible. Store the cluster ID and the retained-document ID rather than silently deleting, so that when a downstream ablation looks wrong you can ask whether dedup ate something it shouldn't have. Deletion is the one step in a data pipeline you cannot re-derive from the output.


      9 · Retention scaffold

      Recall

      1. What is Jaccard similarity and why can't we compute it pairwise on 100M docs?

      J(A,B) = |A ∩ B| / |A ∪ B| on shingle sets. Pairwise is O(N²) = 10^16 comparisons for 100M docs — infeasible.

      2. The MinHash "magic" property in one sentence?

      Pr[minhash(A) = minhash(B)] = J(A, B) exactly. So agreement-fraction across K hashes is an unbiased Jaccard estimator with std error ≈ sqrt(J(1-J)/K).

      3. What does the (b, r) banding do in LSH, and how do you pick them?

      Split the 128-signature into b bands of r hashes. Two docs collide if any band matches exactly. Pr(collide) ≈ 1 - (1 - J^r)^b — an S-curve. Pick (b, r) so the S-curve inflection sits at your target threshold. Standard: b=16, r=8 targets J≈0.8.

      4. Why train a quality classifier instead of just using heuristics?

      Heuristics catch obvious junk (spam, boilerplate). Classifiers catch "prose that looks educational vs looks like a listicle", which is subtler and delivers 3–5 points of downstream gain that heuristics can't touch.

      5. Why sequence dedup BEFORE quality classification, not after?

      Quality classification is the slowest stage (per-doc inference). Dedup first cuts your corpus 30–40% → the expensive stage runs on fewer docs. Cheap stages first, always.

      What 2024–2025 quality-filter pipelines actually do

      MinHash + LSH is table-stakes in 2025 — every serious corpus uses it. But the quality half of "quality + dedup" has evolved dramatically:

      FineWeb-Edu's LLM-as-judge recipe (2024): score 500k random FineWeb docs with Llama-3-70B on a 0–5 "educational value" scale, then train a small classifier (a fine-tuned Snowflake-Arctic-Embed-M with a regression head) to predict that score. Apply the classifier to the whole 15T-token corpus, keep only docs with score >= 3. Result: 1.3T tokens, and models trained on this beat models trained on 5–10× more raw FineWeb. https://huggingface.co/HuggingFaceFW/fineweb-edu-classifier

      DCLM-Baseline's fastText classifier (2024): a linear model, trained on ~200k "positives" (OpenHermes-2.5 + ELI5 answers) vs ~200k "negatives" (random CC), scores docs on how "instruction-answer-like" they read. Keep top 10%. Simpler than FineWeb-Edu, cheaper to run at scale, similar quality on downstream. https://github.com/mlfoundations/dclm

      Nemotron-CC's LLM rewriting (NVIDIA 2024): flip the script — don't discard low-quality docs, rewrite them with a large LLM into higher-quality versions. Doubles usable token count from a fixed CC snapshot.

      And on the dedup side, 2024–2025 pushed further:

      • Suffix-array exact substring dedup (Lee et al. 2021, still standard) — catches 50-char+ repeats that MinHash misses because the docs have different overall structure but share pasted passages.
      • Semantic dedup with embeddings (Abbas et al. 2023, SemDeDup, arxiv 2303.09540) — embed every doc, cluster with FAISS, drop docs whose nearest-neighbor cosine sim is above 0.9. Catches paraphrased duplicates that MinHash misses. Now used in D4 and DCLM.
      • Cross-corpus dedup: FineWeb and DCLM both dedup against known benchmark sets (MMLU, HumanEval, HellaSwag prompts) to prevent contamination. This is now standard hygiene and you should do it too.

      Recommended 2025 stack for a serious pretraining pipeline:

      1. Language ID (fastText lid.176 or GlotLID)
      2. Gopher rules (still work, still cheap)
      3. MinHash + LSH near-dedup (threshold ~0.8)
      4. Suffix-array exact substring dedup (50+ chars)
      5. Semantic dedup (optional, expensive but worth it above 100B tokens)
      6. LLM-judge-trained quality classifier (FineWeb-Edu style)
      7. Benchmark-contamination filter
      8. Tokenize + shard
      

      Steps 1–4 give you FineWeb-quality output. Add steps 5–6 and you're at FineWeb-Edu / DCLM-Baseline quality.

      War story Benchmark contamination is worse than you think

      Dodge et al. (2021, C4 audit) found substantial overlap between C4 and standard NLP eval sets. Since then it's been shown that GPT-3 saw substantial fractions of HumanEval, GSM8K, and MMLU during training. Every 2024+ frontier report now includes a "contamination check" section, and the standard practice is: hash every n-gram of every eval prompt, filter your pretraining corpus against that hash set. Do this even if you're only pretraining a 100M-parameter research model — otherwise your eval numbers are meaningless. The FineWeb team's contamination-filter notebook (https://huggingface.co/spaces/HuggingFaceFW/blogpost-fineweb-v1) is the easiest starting point.

      Stretch

      Implement the (b,r) tuning: write a function plot_scurve(b, r) that plots P(collide) vs J on a grid of J ∈ [0, 1]. Overlay curves for (16,8), (32,4), (8,16), (64,2). Where do they cross? Which one has the sharpest transition? Why does that matter for false-positive rate?

      In your own words

      "MinHash beats pairwise Jaccard because ____ and LSH beats MinHash alone because ____."

      Spaced-review pointer

      • S046 — you were promised near-dedup. This session delivers it. Wire MinHash+LSH into the pipeline where the placeholder was.
      • S048 — a well-deduped mix makes upsampling ratios more accurate (no double-counting).

      Next-session teaser

      Data is done. Session 050 opens Module 09 — pretraining a real foundation model. We start with architecture choices: pre-LN vs post-LN, SwiGLU vs GELU, RoPE vs learned PE, GQA vs MHA. All the "why does every 2024 LLM look the same" questions get answered with the actual empirical evidence.

      Bring back tomorrow

      • Equation: Pr[minhash(A)=minhash(B)] = J(A,B).
      • Recipe: b=16, r=8, num_perm=128, threshold=0.8.
      • Pipeline order: cheap filters → dedup → expensive classifier → tokenize.

      Session recall · click to reveal
      ★ = stretch question

      Previous: ← DL S048 · Next: DL S050 →