Thomas Piacentini
← Workshop

Cheat sheet

Pre-Training Data Pipelines

Original text · English

Clean, dedup, download, tokenize, shard and window a pretraining corpus. Python from the lessons.

Tasks

TaskCode
Run a lesson demopython3 code/main.py
Run the testspython3 -m unittest discover code/tests -v
Drop near duplicatesdeduplicate(filtered_docs, threshold=0.8)
Pack ids into sequencespack_sequences(token_ids, seq_length, pad_id=0)
Download one shardprocess_shard(plan, downloader, dedup, manifest)
Write HDF5 shardspipeline.write_corpus(shards)
Check shard hashesvalidate_corpus(entries)
Read a token rangestore.get_slice(start, stop)
Sources

Corpus to batch

GETbytesdocsverdictsJSONL shardsshards.jsonget_sliceShard URLsShardPlannerclientStreamingDownloaderRange resumeprocessZstdDocIteratorone doc per lineprocessDedupMinHasher + LSHIndexprocessmanifest.jsonshard sha256storeHDF5ShardWriterone .h5 per shardstoreMmapTokenStoreswmr readsstoreSlidingWindowDataloaderinput, targetgpu
Nodes and connections
NodeRoleKind
Shard URLsShardPlannerclient
StreamingDownloaderRange resumeprocess
ZstdDocIteratorone doc per lineprocess
DedupMinHasher + LSHIndexprocess
manifest.jsonshard sha256store
HDF5ShardWriterone .h5 per shardstore
MmapTokenStoreswmr readsstore
SlidingWindowDataloaderinput, targetgpu
FromToConnection
Shard URLsStreamingDownloaderGET
StreamingDownloaderZstdDocIteratorbytes
ZstdDocIteratorDedupdocs
Dedupmanifest.jsonverdicts
manifest.jsonHDF5ShardWriterJSONL shards
HDF5ShardWriterMmapTokenStoreshards.json
MmapTokenStoreSlidingWindowDataloaderget_slice
Sources

Clean and filter

def clean_text(text):
text = re.sub(r"<[^>]+>", "", text)strip HTML tags
text = re.sub(r"http\S+", "", text)drop URLs
text = re.sub(r"[^\x20-\x7E\n]", "", text)printable ASCII only
text = re.sub(r"\n{3,}", "\n\n", text)collapse blank lines
text = re.sub(r" {2,}", " ", text)collapse spaces
return text.strip()
def quality_filter(text, min_words=50, max_ratio_caps=0.3, max_ratio_special=0.1):
...False for stubs, ALL CAPS spam, symbol noise
Sources

Cleaning stages

  1. HTML strip keep only the visible text.
  2. Language detection fastText lid.176.bin. Below 0.8 confidence is not clean English.
  3. Quality filter high perplexity against a Wikipedia model means spam.
  4. Deduplication MinHash plus LSH. Remove one copy above 0.8 Jaccard.
  5. PII removal regex for structured PII, NER models for names.
  6. Tokenize and pack append tokenizer.eos_id after each document.
Sources

Sliding window dataset

ds = SlidingWindowDataset(ids, context_length=16, stride=8)stride None means context_length
SlidingWindowDataset.count_windows(num_ids, context_length, stride)1 + usable // stride
x, y = ds[0]window[:-1], window[1:]
loader = make_dataloader(ds, batch_size=4, base_seed=7, epoch=0)
generator.manual_seed(base_seed + epoch)same seed, same order
Sources

Resume a download

  1. StreamingDownloader reads <shard_id>.partial.json next to <shard_id>.zst.
  2. _verify_partial file size must equal verified_bytes. The sha256 must equal sha256_prefix_hex.
  3. Mismatch delete the shard and checkpoint. Restart from byte zero.
  4. Match send Range as bytes={resume_from}- and open the shard in ab mode.
  5. Response check status must be 206 with a matching Content-Range. Else do a full GET.
  6. Each chunk _write_checkpoint with os.replace, then out.write(buf).
Sources

Dedup and filter defaults

SettingDefaultNote
min_words50Stub pages fail
max_ratio_caps0.3ALL CAPS spam fails
max_ratio_special0.1Symbol noise fails
threshold0.8Jaccard to drop a copy
num_hashes128Signature length
bands16Lesson 03. Lesson 42 uses DEFAULT_BANDS = 32
DEFAULT_SHINGLE_WIDTH5Demo uses shingle_width=3
CHUNK_BYTES1 << 16Download read size
Sources

Storage defaults

SettingDefaultNote
DEFAULT_CHUNK_SIZE8192Demo uses chunk_size=512
DEFAULT_WINDOW_SIZE64Reads window_size + 1 tokens
BOUNDARY_TOKEN_ID0Separator between documents
TOKEN_DTYPEnp.uint16Byte ids offset by one
maxshape(None,)Resizable dataset
libver"latest"Required on the writer for SWMR
Sources

Numbers and formulas

Window count
max(0, 1 + (N - (T + 1)) // S) for N ids, context T, stride S.
Stride
T // 2 doubles the dataset. 1 grows it by a factor of T.
LSH collision
1 - (1 - s^r)^b with k = b * r.
LSH threshold
near (1/b)^(1/r). For b=32, r=4 it is near s = 0.42.
Compression
English is about 3-4 characters per token.
Sequence utilization
below 90% means packing wastes compute.
Dedup yield
the Llama team removed approximately 38% of web data.
Chinchilla
scale parameters and tokens roughly equally.
Sources

Troubleshooting

SymptomCauseFix
Resumed shard holds garbageOffset and file differCheck the sha256 prefix first
Server ignores RangeStatus is not 206Close it and do a full GET
Duplicated suffix after power lossBytes written before checkpointCheckpoint first, then write
Model learns to predict zeroReader reads zero paddingTruncate at token_count
"file is locked"No libver="latest" or late SWMRCreate datasets, then set swmr_mode
Throughput halvesChunk does not match windowChunk a multiple of window_size
Runs diverge for no reasonNo shuffle seedPass base_seed per epoch
ValueError from LSHIndexbands does not divide num_hashesPick bands that divide 128
Sources

Q&A

Why pack instead of pad?
Padding wastes compute on tokens that teach nothing.
Why a block-diagonal mask?
Doc A tokens must not attend to Doc B tokens.
What does the slicer do with a short tail?
It drops the last window.
Why dedup before tokenization?
Tokenization is expensive. Do not pay it twice.
Why tombstone duplicates?
The manifest keeps the link to the keeper.
Why one HDF5 per source shard?
A crash loses at most one shard.
Sources

Tips

  • Trainers read shards.json, not the directory listing.
  • Verify the manifest sha256 in manifest.lock before you trust shard rows.
  • Call dataset.flush() after each resize in SWMR mode.
  • Buffer tokens to chunk size, then resize. Never write one document at a time.
  • Most runs use a stride equal to the context length.
Sources
Original source
---
id: pretraining-data
title: Pre-Training Data Pipelines
code: PP
subtitle: Clean, dedup, download, tokenize, shard and window a pretraining corpus. Python from the lessons.
hue: 28
sources: phases/10-llms-from-scratch/03-data-pipelines, phases/19-capstone-projects/31-tokenized-dataset-sliding-window, phases/19-capstone-projects/42-large-corpus-downloader, phases/19-capstone-projects/43-hdf5-tokenized-corpus
source_hash: 4e29cc278516394f
---

## Tasks
type: task-table
from: phases/10-llms-from-scratch/03-data-pipelines/code/main.py, phases/19-capstone-projects/31-tokenized-dataset-sliding-window/code/main.py, phases/19-capstone-projects/42-large-corpus-downloader/code, phases/19-capstone-projects/43-hdf5-tokenized-corpus/code

| Task | Code |
|---|---|
| Run a lesson demo | `python3 code/main.py` |
| Run the tests | `python3 -m unittest discover code/tests -v` |
| Drop near duplicates | `deduplicate(filtered_docs, threshold=0.8)` |
| Pack ids into sequences | `pack_sequences(token_ids, seq_length, pad_id=0)` |
| Download one shard | `process_shard(plan, downloader, dedup, manifest)` |
| Write HDF5 shards | `pipeline.write_corpus(shards)` |
| Check shard hashes | `validate_corpus(entries)` |
| Read a token range | `store.get_slice(start, stop)` |

## Corpus to batch
type: architecture
from: phases/19-capstone-projects/42-large-corpus-downloader/docs/en.md, phases/19-capstone-projects/43-hdf5-tokenized-corpus/docs/en.md

```arch
node | url | 1,1 | Shard URLs | ShardPlanner | client
node | dl | 2,1 | StreamingDownloader | Range resume | process
node | zst | 3,1 | ZstdDocIterator | one doc per line | process
node | dedup | 4,1 | Dedup | MinHasher + LSHIndex | process
node | man | 4,2 | manifest.json | shard sha256 | store
node | h5 | 3,2 | HDF5ShardWriter | one .h5 per shard | store
node | store | 2,2 | MmapTokenStore | swmr reads | store
node | win | 1,2 | SlidingWindowDataloader | input, target | gpu
edge | url | dl | GET
edge | dl | zst | bytes
edge | zst | dedup | docs
edge | dedup | man | verdicts
edge | man | h5 | JSONL shards
edge | h5 | store | shards.json
edge | store | win | get_slice
```

## Clean and filter
type: annotated-code
lang: python
from: phases/10-llms-from-scratch/03-data-pipelines/code/main.py, phases/10-llms-from-scratch/03-data-pipelines/docs/en.md

```python
def clean_text(text):
    text = re.sub(r"<[^>]+>", "", text)  # strip HTML tags
    text = re.sub(r"http\S+", "", text)  # drop URLs
    text = re.sub(r"[^\x20-\x7E\n]", "", text)  # printable ASCII only
    text = re.sub(r"\n{3,}", "\n\n", text)  # collapse blank lines
    text = re.sub(r" {2,}", " ", text)  # collapse spaces
    return text.strip()

def quality_filter(text, min_words=50, max_ratio_caps=0.3, max_ratio_special=0.1):
    ...  # False for stubs, ALL CAPS spam, symbol noise
```

## Cleaning stages
type: flow
from: phases/10-llms-from-scratch/03-data-pipelines/docs/en.md

1. **HTML strip**: keep only the visible text.
2. **Language detection**: fastText lid.176.bin. Below 0.8 confidence is not clean English.
3. **Quality filter**: high perplexity against a Wikipedia model means spam.
4. **Deduplication**: MinHash plus LSH. Remove one copy above 0.8 Jaccard.
5. **PII removal**: regex for structured PII, NER models for names.
6. **Tokenize and pack**: append `tokenizer.eos_id` after each document.

## Sliding window dataset
type: annotated-code
lang: python
from: phases/19-capstone-projects/31-tokenized-dataset-sliding-window/code/main.py

```python
ds = SlidingWindowDataset(ids, context_length=16, stride=8)  # stride None means context_length
SlidingWindowDataset.count_windows(num_ids, context_length, stride)  # 1 + usable // stride
x, y = ds[0]  # window[:-1], window[1:]
loader = make_dataloader(ds, batch_size=4, base_seed=7, epoch=0)
generator.manual_seed(base_seed + epoch)  # same seed, same order
```

## Resume a download
type: flow
from: phases/19-capstone-projects/42-large-corpus-downloader/code/main.py, phases/19-capstone-projects/42-large-corpus-downloader/docs/en.md

1. **StreamingDownloader**: reads `<shard_id>.partial.json` next to `<shard_id>.zst`.
2. **_verify_partial**: file size must equal `verified_bytes`. The sha256 must equal `sha256_prefix_hex`.
3. **Mismatch**: delete the shard and checkpoint. Restart from byte zero.
4. **Match**: send `Range` as `bytes={resume_from}-` and open the shard in `ab` mode.
5. **Response check**: status must be 206 with a matching `Content-Range`. Else do a full GET.
6. **Each chunk**: `_write_checkpoint` with `os.replace`, then `out.write(buf)`.

## Dedup and filter defaults
type: defaults
from: phases/10-llms-from-scratch/03-data-pipelines/code/main.py, phases/19-capstone-projects/42-large-corpus-downloader/code/main.py

| Setting | Default | Note |
|---|---|---|
| `min_words` | 50 | Stub pages fail |
| `max_ratio_caps` | 0.3 | ALL CAPS spam fails |
| `max_ratio_special` | 0.1 | Symbol noise fails |
| `threshold` | 0.8 | Jaccard to drop a copy |
| `num_hashes` | 128 | Signature length |
| `bands` | 16 | Lesson 03. Lesson 42 uses `DEFAULT_BANDS = 32` |
| `DEFAULT_SHINGLE_WIDTH` | 5 | Demo uses `shingle_width=3` |
| `CHUNK_BYTES` | `1 << 16` | Download read size |

## Storage defaults
type: defaults
from: phases/19-capstone-projects/43-hdf5-tokenized-corpus/code/main.py, phases/19-capstone-projects/43-hdf5-tokenized-corpus/docs/en.md

| Setting | Default | Note |
|---|---|---|
| `DEFAULT_CHUNK_SIZE` | 8192 | Demo uses `chunk_size=512` |
| `DEFAULT_WINDOW_SIZE` | 64 | Reads `window_size + 1` tokens |
| `BOUNDARY_TOKEN_ID` | 0 | Separator between documents |
| `TOKEN_DTYPE` | `np.uint16` | Byte ids offset by one |
| `maxshape` | `(None,)` | Resizable dataset |
| `libver` | `"latest"` | Required on the writer for SWMR |

## Numbers and formulas
type: concepts
from: phases/10-llms-from-scratch/03-data-pipelines/docs/en.md, phases/19-capstone-projects/31-tokenized-dataset-sliding-window/docs/en.md, phases/19-capstone-projects/42-large-corpus-downloader/docs/en.md, phases/19-capstone-projects/42-large-corpus-downloader/code/main.py

- **Window count**: `max(0, 1 + (N - (T + 1)) // S)` for N ids, context T, stride S.
- **Stride**: `T // 2` doubles the dataset. `1` grows it by a factor of `T`.
- **LSH collision**: `1 - (1 - s^r)^b` with `k = b * r`.
- **LSH threshold**: near `(1/b)^(1/r)`. For b=32, r=4 it is near s = 0.42.
- **Compression**: English is about 3-4 characters per token.
- **Sequence utilization**: below 90% means packing wastes compute.
- **Dedup yield**: the Llama team removed approximately 38% of web data.
- **Chinchilla**: scale parameters and tokens roughly equally.

## Troubleshooting
type: troubleshooting
from: phases/19-capstone-projects/42-large-corpus-downloader/docs/en.md, phases/19-capstone-projects/42-large-corpus-downloader/code/main.py, phases/19-capstone-projects/43-hdf5-tokenized-corpus/docs/en.md, phases/19-capstone-projects/31-tokenized-dataset-sliding-window/docs/en.md, phases/10-llms-from-scratch/03-data-pipelines/docs/en.md

| Symptom | Cause | Fix |
|---|---|---|
| Resumed shard holds garbage | Offset and file differ | Check the sha256 prefix first |
| Server ignores `Range` | Status is not 206 | Close it and do a full GET |
| Duplicated suffix after power loss | Bytes written before checkpoint | Checkpoint first, then write |
| Model learns to predict zero | Reader reads zero padding | Truncate at `token_count` |
| "file is locked" | No `libver="latest"` or late SWMR | Create datasets, then set `swmr_mode` |
| Throughput halves | Chunk does not match window | Chunk a multiple of `window_size` |
| Runs diverge for no reason | No shuffle seed | Pass `base_seed` per epoch |
| `ValueError` from `LSHIndex` | `bands` does not divide `num_hashes` | Pick bands that divide 128 |

## Q&A
type: qa
from: phases/10-llms-from-scratch/03-data-pipelines/docs/en.md, phases/19-capstone-projects/31-tokenized-dataset-sliding-window/docs/en.md, phases/19-capstone-projects/42-large-corpus-downloader/docs/en.md, phases/19-capstone-projects/43-hdf5-tokenized-corpus/docs/en.md

- **Why pack instead of pad?** Padding wastes compute on tokens that teach nothing.
- **Why a block-diagonal mask?** Doc A tokens must not attend to Doc B tokens.
- **What does the slicer do with a short tail?** It drops the last window.
- **Why dedup before tokenization?** Tokenization is expensive. Do not pay it twice.
- **Why tombstone duplicates?** The manifest keeps the link to the keeper.
- **Why one HDF5 per source shard?** A crash loses at most one shard.

## Tips
type: tips
from: phases/19-capstone-projects/42-large-corpus-downloader/docs/en.md, phases/19-capstone-projects/43-hdf5-tokenized-corpus/docs/en.md, phases/19-capstone-projects/31-tokenized-dataset-sliding-window/docs/en.md

- Trainers read `shards.json`, not the directory listing.
- Verify the manifest sha256 in `manifest.lock` before you trust shard rows.
- Call `dataset.flush()` after each resize in SWMR mode.
- Buffer tokens to chunk size, then resize. Never write one document at a time.
- Most runs use a stride equal to the context length.
Code and artifacts 1
What you take away

Saved in your notebook on this device.

Sources and limits

Reading checkpoints are self-reported. Test results are recorded separately and apply to the executed cases.

AI Engineering from Scratch · 3f9715c · MIT

Download this file ↓