Skip to content

Commit b45fb36

Browse files
davanstrienclaudelhoestq
authored
Add Jobs guide: Process Large Datasets (#2522)
* Add Process Large Datasets guide for Jobs New docs/hub/jobs-large-datasets.md: how to read, filter, and process datasets larger than a Job's ephemeral disk — streaming, mounting + lazy reads, reading/filtering over hf:// with Polars/DuckDB, saving results to a bucket, and a Common Crawl worked example. Links canonical pages rather than restating them; adds an inbound pointer from jobs-configuration#volumes. * small edits * Verify snippets on live Jobs; rework hf:// and mount sections from results Every example was run against real Jobs. Key changes from what testing showed: - hf:// section: native Polars scan (verified ~4 min for the 28 GB sample aggregate on the default CPU flavor) replaces the register_filesystem(HfFileSystem()) DuckDB example, which could not finish the same scan within the 30-minute default timeout. Native hf:// stays for datasets; HfFileSystem noted for buckets. - Mount section: reframed for whole-file/local-path access; large Parquet scans pointed at hf:// instead (measured several times slower through the mount). Added the missing PEP 723 header to the script example. - Save results: hosts the DuckDB COPY-to-bucket pattern (out-of-core write), scaled from 100BT to 10BT, with --timeout shown and a note that it transfers the full text column. - Added --timeout guidance (long scans are network-bound), real output for the Common Crawl example (verified verbatim, ~1 min, no token needed), hf jobs hardware mention, and Title Case/heading consistency. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix Common Crawl link: use buckets path, not datasets commoncrawl/commoncrawl is a Storage Bucket, not a dataset — the datasets/ URL returns 401. Matches the buckets/ path already used in the worked example just below. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Tighten intro + Save results wording, trim Common Crawl table Editorial polish after review: rewrite the intro (cut repetition, name the read paths), tighten the Save-results paragraph (drop a caveat already made in the Read/filter section), and truncate the Common Crawl output table to the top languages. Every code example re-verified against live Jobs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Use a heading for 'Which approach?' to match house style Sibling Jobs pages use ## headings, not bold pseudo-headings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Update docs/hub/jobs-large-datasets.md Co-authored-by: Quentin Lhoest <42851186+lhoestq@users.noreply.github.com> * Update docs/hub/jobs-large-datasets.md Co-authored-by: Quentin Lhoest <42851186+lhoestq@users.noreply.github.com> * Update docs/hub/jobs-large-datasets.md Co-authored-by: Quentin Lhoest <42851186+lhoestq@users.noreply.github.com> * Apply remaining review suggestions: Spark distributed streaming, hffs version floor Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Quentin Lhoest <42851186+lhoestq@users.noreply.github.com>
1 parent 892dc27 commit b45fb36

3 files changed

Lines changed: 175 additions & 0 deletions

File tree

docs/hub/_toctree.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,8 @@
460460
title: Serve Models
461461
- local: jobs-examples
462462
title: Examples & Tutorials
463+
- local: jobs-large-datasets
464+
title: Process Large Datasets
463465
- local: jobs-schedule
464466
title: Schedule Jobs
465467
- local: jobs-webhooks

docs/hub/jobs-configuration.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ You can pass environment variables to your job using
105105

106106
Mount Hugging Face repositories (models, datasets), [Storage Buckets](./storage-buckets), or local directories as volumes in your job container using `-v` or `--volume`. Hub sources use the `hf://` URL scheme: `hf://[TYPE/]SOURCE:/MOUNT_PATH[:ro]`; a local directory is passed directly as the source.
107107

108+
> [!TIP]
109+
> Because mounted files are fetched lazily, mounting lets a Job work with datasets far larger than its local disk. See [Process Large Datasets](./jobs-large-datasets) for mounting, streaming, and processing big data on Jobs.
110+
108111
Volume types:
109112

110113
| Type | Example |

docs/hub/jobs-large-datasets.md

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Process Large Datasets
2+
3+
Every Job comes with a fixed amount of local disk, set by its [hardware flavor](./jobs-pricing#pricing) (the **Ephemeral Storage** column, also shown by `hf jobs hardware`). You don't need to fit a whole dataset on that disk to work with it: datasets and [Storage Buckets](./storage-buckets) can be read directly from the Hub — streamed, queried, or mounted — so a single Job can process data far larger than its disk. This page covers the options and when to reach for each.
4+
5+
## Which approach?
6+
7+
- **Fits on disk** → a plain `load_dataset(...)` works as-is (or just pick a bigger flavor — up to 1 TB of ephemeral disk).
8+
- **Iterating over rows, processing or training**[stream](#stream-the-dataset) it.
9+
- **Filtered or column-pruned scans** → query it [directly over `hf://`](#read-and-filter-over-hf) with Polars or DuckDB.
10+
- **Tools that expect local file paths**[mount](#mount-a-dataset-model-or-bucket) the repo and read it lazily.
11+
- **Persisting results** → write them to a [Storage Bucket](#save-results) so they survive the Job.
12+
13+
## Stream the dataset
14+
15+
Streaming reads examples from the Hub as your code consumes them — no download, no local copy, and it scales to multi-TB datasets. Recent releases made it [up to 100× more efficient](https://huggingface.co/blog/streaming-datasets), reaching performance on par with local SSDs when training across many workers:
16+
17+
```python
18+
from datasets import load_dataset
19+
20+
ds = load_dataset("HuggingFaceFW/fineweb-edu", "sample-10BT", split="train", streaming=True)
21+
for example in ds.take(1000):
22+
... # streams in as you iterate, nothing hits disk
23+
```
24+
25+
A streamed dataset is an `IterableDataset` supporting lazy `.filter()`, `.map()`, `.shuffle(buffer_size=...)`, and `.batch()`, and can be passed straight to a PyTorch `DataLoader` or a `Trainer` to train on data larger than disk. See the [Stream guide](/docs/datasets/stream) for the full API, and [Examples & Tutorials](./jobs-examples) for end-to-end training walkthroughs for single-node or distributed setups.
26+
27+
Streaming also works in distributed setups, using Spark:
28+
29+
```python
30+
import pyspark_huggingface
31+
32+
df = spark.read.format("huggingface").option("config", "sample-10BT").load("HuggingFaceFW/fineweb-edu")
33+
```
34+
35+
The resulting Spark dataframe is distributed: each worker streams from its own subset of files. See the [Spark documentation](./datasets-spark) for examples of reading, writing, and efficiently filtering rows and columns.
36+
37+
## Read and filter over `hf://`
38+
39+
Many data libraries read Hub datasets directly over `hf://` paths — **Polars**, **DuckDB**, and **pandas** all scan Hub Parquet natively, pushing filters and column selection down into the scan, so a single Job can process far more data than fits in memory or on disk. This query summarizes ~28 GB of Parquet in about four minutes on the default CPU flavor:
40+
41+
```python
42+
import polars as pl
43+
44+
agg = (
45+
pl.scan_parquet("hf://datasets/HuggingFaceFW/fineweb-edu/sample/10BT/*.parquet")
46+
.filter(pl.col("int_score") >= 4)
47+
.group_by("int_score")
48+
.agg(pl.len().alias("docs"), pl.col("token_count").sum().alias("tokens"))
49+
.sort("int_score")
50+
.collect()
51+
)
52+
print(agg)
53+
```
54+
55+
Scans like this are network-bound rather than memory-bound, and engines differ in how aggressively they parallelize remote reads, so timings vary by library. Two practical consequences: set `--timeout` above the default 30 minutes for long scans, and split the work across several Jobs running in parallel to go faster. See [Polars](./datasets-polars), [DuckDB](./datasets-duckdb), and [pandas](./datasets-pandas) for per-library examples, and [Python data tools](./storage-buckets-access#python-data-tools) for reading **buckets** over `hf://` (which goes through [`HfFileSystem`](/docs/huggingface_hub/guides/hf_file_system)).
56+
57+
## Mount a dataset, model, or bucket
58+
59+
Mount a repository or bucket into the Job as a local path with `-v` / `--volume`; files are fetched lazily over the network as your code reads them, so any tool that reads local files just works:
60+
61+
```bash
62+
hf jobs uv run --flavor cpu-upgrade \
63+
-v hf://datasets/HuggingFaceFW/fineweb-edu:/mnt/data \
64+
process.py
65+
```
66+
67+
```python
68+
# /// script
69+
# dependencies = ["polars"]
70+
# ///
71+
# process.py — the mounted repo is just a directory of files
72+
from pathlib import Path
73+
74+
import polars as pl
75+
76+
for path in Path("/mnt/data/sample/10BT").glob("*.parquet"):
77+
shard = pl.read_parquet(path)
78+
... # process one shard at a time, write results out
79+
```
80+
81+
Mounting is the natural fit when files are consumed whole — model weights, audio or image files, archives — or when a tool only accepts file paths. For large multi-file Parquet scans, querying [directly over `hf://`](#read-and-filter-over-hf) is typically several times faster than scanning through a mount.
82+
83+
Datasets and models mount read-only; buckets are read-write, which makes them a good place to [save results](#save-results). See [Configuration](./jobs-configuration#volumes) for the full `-v` syntax and [bucket access patterns](./storage-buckets-access#volume-mounts-in-jobs-and-spaces) for details.
84+
85+
> [!TIP]
86+
> Files read through a mount are cached on the Job's ephemeral disk, so reading lazily (one file at a time) keeps the footprint small. When running a local script with `hf jobs uv run`, the script directory is mounted at `/data`, so mount your data elsewhere (e.g. `/mnt/data`).
87+
88+
## Save results
89+
90+
Ephemeral disk doesn't survive the Job, so write anything you want to keep to a [Storage Bucket](./storage-buckets) mounted read-write, or push it to the Hub as a dataset. DuckDB can filter the source over `hf://` and write the matches straight to a mounted bucket in one out-of-core query, so the result never has to fit in memory:
91+
92+
```bash
93+
hf jobs uv run --flavor cpu-upgrade --timeout 1h \
94+
-v hf://buckets/username/my-output:/mnt/out \
95+
filter.py
96+
```
97+
98+
```python
99+
# /// script
100+
# dependencies = ["duckdb"]
101+
# ///
102+
# filter.py — scan ~28 GB of Parquet, keep only the matching rows
103+
import duckdb
104+
105+
duckdb.sql(
106+
"""
107+
COPY (
108+
SELECT text, url, token_count
109+
FROM 'hf://datasets/HuggingFaceFW/fineweb-edu/sample/10BT/*.parquet'
110+
WHERE int_score >= 4 AND token_count >= 4000
111+
) TO '/mnt/out/result.parquet' (FORMAT parquet)
112+
"""
113+
)
114+
```
115+
116+
Files written under the bucket mount path persist after the Job ends. To publish a processed dataset instead, use [`Dataset.push_to_hub`](/docs/datasets/upload_dataset).
117+
118+
## Worked example: query Common Crawl without downloading it
119+
120+
Common Crawl mirrors its archive to the bucket [`commoncrawl/commoncrawl`](https://huggingface.co/buckets/commoncrawl/commoncrawl) — hundreds of TB. Stream one WET (plaintext) shard straight from `hf://`, parse it, and query it with DuckDB; only a few MB transit because the gzip is read sequentially and stopped early:
121+
122+
```python
123+
# /// script
124+
# requires-python = ">=3.11"
125+
# dependencies = ["huggingface_hub>=1.9", "fastwarc>=0.15", "duckdb>=1.0"]
126+
# ///
127+
import duckdb
128+
from fastwarc.warc import ArchiveIterator, WarcRecordType
129+
from huggingface_hub import hffs
130+
131+
WET = (
132+
"buckets/commoncrawl/commoncrawl/crawl-data/CC-MAIN-2026-17/"
133+
"segments/1775805908305.14/wet/"
134+
"CC-MAIN-20260410081153-20260410111153-00000.warc.wet.gz"
135+
)
136+
137+
rows = []
138+
with hffs.open(WET, "rb") as f:
139+
for rec in ArchiveIterator(f, record_types=WarcRecordType.conversion):
140+
lang = (rec.headers.get("WARC-Identified-Content-Language", "") or "und").split(",")[0]
141+
rows.append((rec.headers.get("WARC-Target-URI", ""), lang, len(rec.reader.read())))
142+
if len(rows) >= 5000:
143+
break
144+
145+
con = duckdb.connect()
146+
con.execute("CREATE TABLE wet(url VARCHAR, lang VARCHAR, n_chars BIGINT)")
147+
con.executemany("INSERT INTO wet VALUES (?,?,?)", rows)
148+
con.sql("SELECT lang, count(*) AS docs FROM wet GROUP BY lang ORDER BY docs DESC LIMIT 10").show()
149+
```
150+
151+
Run it with `hf jobs uv run cc_wet.py` — it completes in about a minute on the default CPU flavor and prints:
152+
153+
```
154+
┌─────────┬───────┐
155+
│ lang │ docs │
156+
│ varchar │ int64 │
157+
├─────────┼───────┤
158+
│ eng │ 1974 │
159+
│ zho │ 586 │
160+
│ rus │ 434 │
161+
│ jpn │ 244 │
162+
│ … │ … │
163+
└─────────┴───────┘
164+
```
165+
166+
## See also
167+
168+
- [Stream](/docs/datasets/stream) · [Streaming datasets: 100× more efficient](https://huggingface.co/blog/streaming-datasets)
169+
- [Pricing & hardware](./jobs-pricing#pricing) — ephemeral disk per flavor · [Configuration](./jobs-configuration#volumes) — volumes
170+
- [Storage Buckets](./storage-buckets) · [access patterns](./storage-buckets-access) · [integrations](./storage-buckets-integrations)

0 commit comments

Comments
 (0)