pyroid.integrations¶
Plug-ins for the tools you already run. import pyroid imports none of them, and each needs an
extra: pyroid[datatrove], pyroid[nemo], pyroid[hf] or pyroid[cli] (for recipes). Guides:
datatrove steps, NeMo Curator stages, Hugging Face datasets,
the command line, pyroid.patch().
These modules have no stubs: this page is generated from their source.
Patching: pyroid.integrations¶
pyroid.patch and pyroid.unpatch are the same functions as pyroid.integrations.patch and
pyroid.integrations.unpatch.
pyroid for datatrove and NeMo Curator pipelines, Hugging Face datasets and existing imports.
import pyroid imports none of this, and nothing changes behaviour until you
call it.
pyroid.integrations.datatrove: 15 drop-in datatrove pipeline steps (FTFYFormatter,LanguageFilter, the Gopher/C4/FineWeb quality filters,C4BadWordsFilter,URLFilter,PIIFormatter,SymbolLinesFormatter,MinhashDedupSignatureand the four sentence-dedup steps) that subclass datatrove's own classes and give the same output. The steps that tokenize run in Rust for every language pyroid tokenizes natively.pyroid.integrations.nemo_curator: NeMo Curator 1.4.0'sScoreFilter,Score,FilterandModifystages, its 23 heuristic filters, two fastText filters,UnicodeReformatterand string modifiers, subclassing NeMo's classes, with the same output, one pyroid call per batch.pyroid.integrations.recipes: the FineWeb cleaning chain over Arrow batches (whatpyroid clean --recipe finewebruns).pyroid.integrations.hf: helpers for Hugging Facedatasets(Dataset.map(batched=True)/.filteron Arrow batches).patch/unpatch/patching: explicit, reversiblesys.modulesshims so that existingimport ftfy/import fasttextcode gets pyroid's drop-ins without edits (alsopyroid run -p ftfy,fasttext script.py).
PatchWarning ¶
Bases: UserWarning
patch replaced a module with pyroid's drop-in.
Says what was patched, which version pyroid reproduces and how to undo it; shown once per
process for each name, plus once more if the installed package is another version. Silence
it with warnings.simplefilter("ignore", pyroid.integrations.PatchWarning).
patch ¶
patch(*names: str) -> None
Make import <name> give pyroid's drop-in, for each of names.
patch("ftfy", "fasttext") puts shims in sys.modules, so a later import ftfy,
from ftfy import fix_text, import fasttext.FastText... gets pyroid's functions, with
everything pyroid does not provide forwarded to the real package (imported now, if it is
installed). Modules that imported the real package earlier keep it. Patching a patched name
does nothing; unpatch undoes it.
Parameters:
-
*names(str, default:()) –"ftfy","fasttext","anyascii","datasketch","trafilatura","nltk","warcio"and/or"nemo_curator".
Raises:
-
TypeError–No names were given.
-
ValueError–A name is not supported (nothing is patched then).
-
ImportError–The pyroid module behind a name cannot be imported (
pyroid.fasttextandpyroid.datasketchneed numpy).
Warns:
-
PatchWarning–Once per process for each name: what was patched and how to undo it; and if the installed package is not the version pyroid reproduces.
unpatch ¶
unpatch(*names: str) -> None
Undo patch for names (all patched names if none are given).
sys.modules gets back exactly the entries it had for each name and its submodules
before patch (the real module if it was imported, else no entry; submodules
imported meanwhile are dropped, so the next import is fresh), and pyroid's fallback to the
real package is restored. Modules that imported a shim keep it, and it keeps working.
Names that are not patched are ignored.
Raises:
-
ValueError–A name is not supported.
patching ¶
patching(*names: str) -> AbstractContextManager[None]
patch names for the duration of a with block.
On exit, only the names this block patched are unpatched (one patched before stays).
Raises:
-
TypeError–No names were given.
-
ValueError–A name is not supported.
run ¶
pyroid run [-p NAMES] (-m MODULE | SCRIPT) [ARGS...]: run a target with patches.
Patches NAMES (comma-separated; default $PYROID_PATCH, else ftfy,fasttext,
skipping fasttext with a PatchWarning if numpy is missing), exports
PYROID_PATCH and puts HOOK_DIR first on PYTHONPATH (so child Python
processes are patched too), then runs the target in this process with runpy, as
python -m MODULE ARGS / python SCRIPT ARGS would: sys.argv, sys.path[0],
__name__ == "__main__". It is meant to be the last thing a process does: the patches,
the environment and sys.argv stay as they are afterwards.
Parameters:
Returns:
-
int–0 when the target finishes; 2 for a usage error (message on stderr) or a missing script; 1 when the module cannot be found (as
python).
Raises:
-
SystemExit–The target exited (
sys.exit); its code is the exit code. -
BaseException–Whatever else the target raises propagates, traceback and all.
Constants¶
The names patch() accepts, pyroid run's defaults, and the start-up hook's directory (put it on
PYTHONPATH, with the names in PYROID_PATCH, to patch Python processes started without
pyroid run):
pyroid.integrations.datatrove¶
Each step subclasses datatrove's step of the same name; its constructor takes datatrove's
arguments plus the keyword-only batch_size and threads (and model_path for
LanguageFilter, compat for Trafilatura).
Drop-in datatrove pipeline steps backed by pyroid.
Change the import and leave the pipeline as it is:
# from datatrove.pipeline.filters import GopherQualityFilter, LanguageFilter
from pyroid.integrations.datatrove import GopherQualityFilter, LanguageFilter
Each class here subclasses datatrove's own step of the same name and takes the same constructor arguments, plus two keyword-only ones:
batch_size: documents per pyroid call (default 1000).threads: pyroid'sthreads=(default 1, since datatrove already runs one task per process). PassNonefor pyroid's whole thread pool when you run few tasks.
The documents that come out are the same as with datatrove's step: same
text, same metadata, same keep/drop decisions and drop reasons, so
exclusion writers and later steps see the same data. The work happens in Rust,
in batches, without the GIL:
FTFYFormatter:pyroid.ftfy(byte-identical to ftfy 6.3.1; ftfy itself is not needed).LanguageFilter: datatrove's ownfilter()andFastTextLID.predictcode, with the fastText call replaced by one batchedpyroid.fasttextprediction (nofasttext-numpy2-wheelorfastenersneeded whenmodel_pathis given; without it the model is downloaded to datatrove's own cache, as datatrove does).GopherRepetitionFilter,GopherQualityFilter,C4QualityFilter,FineWebQualityFilter:pyroid.filters(spaCy is not needed).MinhashDedupSignature:pyroid.dedupsignatures, written to the same files, byte for byte, so datatrove'sMinhashDedupBuckets->MinhashDedupCluster->MinhashDedupFilterrun on them unchanged.PIIFormatter,SymbolLinesFormatter:pyroid.filters(same text, same replacement rotation).C4BadWordsFilter,URLFilter:pyroid.filters(same decisions and reasons; datatrove's own random draws, word lists and blocklists).SentenceDedupSignature,SentenceFindDedups,SentenceDedupFilter,SentenceDedupBuildIndex:pyroid.dedup; the same signature, duplicate and index files, byte for byte, and the same documents out.Trafilatura:pyroid.trafilatura(byte-identical to the installed trafilatura, 1.11.0 as datatrove installs it, or 2.3.1; in this process, with no timeout).WarcReader:pyroid.warc.iter_documents(warcio 1.8.2's parsing, the gzip layer and datatrove'sprocess_recordin Rust; the same documents and errors; warcio is not needed). It takes datatrove's arguments only.
Languages: the filter and MinHash steps take datatrove's language (a
datatrove language code such as "fra_Latn"). Every code pyroid tokenizes
natively, which is 4,049 of datatrove's 4,343 with a SudachiDict dictionary and
PyThaiNLP installed and 4,025 with neither (pyroid.filters.languages(),
Language.native), runs in Rust with that language's datatrove tokenizer
reproduced exactly. Any other code (the Stanza languages, Korean, Japanese without
a dictionary, Thai without PyThaiNLP, ...), a code datatrove has no tokenizer for, or a
WordTokenizer object runs datatrove's own code for that step, with a
one-time pyroid.FallbackWarning.
What differs: per-line statistics of C4QualityFilter (line-total,
line-filter-*) are not counted, and batched steps also count batches.
Needs datatrove (and numpy for LanguageFilter/MinhashDedupSignature):
pip install 'pyroid[datatrove]'.
FTFYFormatter ¶
datatrove's FTFYFormatter (same arguments and defaults) on pyroid.ftfy.
doc.text becomes ftfy.fix_text(doc.text, config=self.config), byte for
byte, computed batch_size documents at a time.
LanguageFilter ¶
LanguageFilter(
*args: Any,
model_path: Any = None,
batch_size: int = DEFAULT_BATCH_SIZE,
threads: Any = 1,
**kwargs: Any,
)
datatrove's LanguageFilter (same arguments) on pyroid.fasttext.
Extra keyword-only arguments: model_path (a local lid.176.bin or GlotLID
.bin; default: download to datatrove's cache, as datatrove does),
batch_size and threads.
The decision and the language / language_score (and
language_script, top_language_*_score) metadata come from datatrove's
own filter() code. When only the top label can matter (no
keep_top_pairs_threshold, and no languages, label_only, or a
threshold above 0.501, since fastText's probabilities sum to at most
1 + 176e-5), pyroid predicts the top label only instead of all of them.
GopherRepetitionFilter ¶
GopherRepetitionFilter(
*args: Any,
batch_size: int = DEFAULT_BATCH_SIZE,
threads: Any = 1,
**kwargs: Any,
)
datatrove's GopherRepetitionFilter (same arguments) on pyroid.filters.
GopherQualityFilter ¶
GopherQualityFilter(
*args: Any,
batch_size: int = DEFAULT_BATCH_SIZE,
threads: Any = 1,
**kwargs: Any,
)
datatrove's GopherQualityFilter (same arguments) on pyroid.filters.
C4QualityFilter ¶
C4QualityFilter(
*args: Any,
batch_size: int = DEFAULT_BATCH_SIZE,
threads: Any = 1,
**kwargs: Any,
)
datatrove's C4QualityFilter (same arguments) on pyroid.filters.
Kept documents get the same line removal; per-line statistics are not counted.
FineWebQualityFilter ¶
FineWebQualityFilter(
*args: Any,
batch_size: int = DEFAULT_BATCH_SIZE,
threads: Any = 1,
**kwargs: Any,
)
datatrove's FineWebQualityFilter (same arguments) on pyroid.filters.
MinhashDedupSignature ¶
MinhashDedupSignature(
*args: Any,
batch_size: int = DEFAULT_BATCH_SIZE,
threads: Any = 1,
**kwargs: Any,
)
datatrove's MinhashDedupSignature (stage 1) on pyroid.dedup.
Writes the same bucket_XXX/RANK.minhash.sig files, byte for byte (the
sort pass is datatrove's own), so datatrove's later MinHash stages run on
them unchanged. spaCy is not needed. language is datatrove's: shingles
come from that language's word tokenizer, in Rust for every code pyroid
tokenizes natively; another one runs datatrove's own step
(pyroid.FallbackWarning). Note that datatrove's sort pass logs
"Found existing sig files ... Skipping sig writing step": pyroid has just
written them.
PIIFormatter ¶
datatrove's PIIFormatter (same arguments and defaults) on pyroid.filters.
doc.text gets the same email and IP replacements, byte for byte, in the
same rotation: the counters are datatrove's own (emails_replacer._replace_i
and ip_replacer._replace_i), and whether an address is replaced is asked
of datatrove's own validator, once per distinct address and batch. The
regexes run in Rust, batch_size documents at a time.
C4BadWordsFilter ¶
C4BadWordsFilter(
*args: Any,
batch_size: int = DEFAULT_BATCH_SIZE,
threads: Any = 1,
**kwargs: Any,
)
datatrove's C4BadWordsFilter (same arguments) on pyroid.filters.
The word search runs in Rust, batch_size documents at a time; the
lists are loaded exactly as datatrove loads them (its download cache and
pinned URLs). Everything else is datatrove's own: self.uniform (its
default_rng(seed)) is drawn once per flagged document in order, and the
same statistics are counted. A batch that contains a document whose
language has no list raises before any of the batch is yielded (datatrove
yields the documents before it first).
URLFilter ¶
datatrove's URLFilter (same arguments) on pyroid.filters.URLFilter.
The same keep/drop decisions and filter_reason strings (domain,
subdomain, url, hard_blacklisted, soft_blacklisted,
blacklisted_subword), from doc.metadata["url"], batch_size URLs per
pyroid call. The suffix lists are this step's own tldextractor's (its
cached Public Suffix List or tldextract's snapshot), and with
use_integrated_lists the block lists are datatrove's, extracted into
datatrove's cache as datatrove does; both are loaded at the first batch, in the
worker. pyahocorasick is not needed.
What differs: a document without a url raises datatrove's
AssertionError when its batch is filtered (before any document of that batch
is passed on); datatrove's per-document DeprecationWarning from tldextract's
registered_domain (hidden by Python's default filters) is not emitted;
block_listed_domains and the other list attributes hold the constructor's
entries only (the integrated lists live in Rust); banned_subwords_automaton
is None.
download_data ¶
download_data() -> None
Build the pyroid filter: the lists, and datatrove's if use_integrated_lists.
SymbolLinesFormatter ¶
SymbolLinesFormatter(
*args: Any,
batch_size: int = DEFAULT_BATCH_SIZE,
threads: Any = 1,
**kwargs: Any,
)
datatrove's SymbolLinesFormatter (same arguments) on pyroid.filters.
doc.text becomes the same text, byte for byte, computed batch_size
documents at a time (FineWeb-2 runs it as
SymbolLinesFormatter(symbols_to_remove=["|"], replace_char="\n")).
SentenceDedupSignature ¶
SentenceDedupSignature(
*args: Any,
batch_size: int = DEFAULT_BATCH_SIZE,
threads: Any = 1,
**kwargs: Any,
)
datatrove's SentenceDedupSignature (stage 1) on pyroid.dedup.
Writes the same {bucket:04d}/{rank:05d}.c4_sig files, byte for byte (the
same buckets, the same empty files), so datatrove's own later stages run on them
unchanged. spaCy is not needed. language is datatrove's: every code pyroid tokenizes
natively (pyroid.filters.languages()) runs in Rust; another one with
split_sentences=True runs datatrove's own code (pyroid.FallbackWarning).
SentenceFindDedups ¶
SentenceFindDedups(
*args: Any,
batch_size: int = DEFAULT_BATCH_SIZE,
threads: Any = 1,
**kwargs: Any,
)
datatrove's SentenceFindDedups (stage 2) as a merge in Rust.
Reads the same signature (and index) files and writes the same
{rank:04d}/{stem}.c4_dup files, byte for byte; local files are read by
Rust directly, remote ones through fsspec. batch_size and threads are
accepted for uniformity (the merge is one sequential pass).
SentenceDedupFilter ¶
SentenceDedupFilter(
*args: Any,
batch_size: int = DEFAULT_BATCH_SIZE,
threads: Any = 1,
**kwargs: Any,
)
datatrove's SentenceDedupFilter (stage 3) on pyroid.dedup.
Reads the same .c4_dup files and yields the same documents with the same
texts; the exclusion writer gets the same documents (>>> / <<<-marked
texts). Only documents with duplicates go to Rust, batch_size at a time.
spaCy is not needed. language is datatrove's: a code pyroid does not tokenize
natively (pyroid.filters.languages()) that needs the tokenizer runs datatrove's own
code (pyroid.FallbackWarning).
SentenceDedupBuildIndex ¶
SentenceDedupBuildIndex(
*args: Any,
batch_size: int = DEFAULT_BATCH_SIZE,
threads: Any = 1,
**kwargs: Any,
)
datatrove's SentenceDedupBuildIndex as a merge in Rust (the same index file).
Like datatrove's own step, it fails (StopIteration) on an empty signature file:
that case runs datatrove's code. batch_size and threads are accepted for
uniformity.
Trafilatura ¶
Trafilatura(
favour_precision: bool = True,
include_images: bool = False,
timeout: float = 1,
deduplicate: Any = True,
*,
compat: str | None = None,
batch_size: int = TRAFILATURA_BATCH_SIZE,
threads: Any = 1,
**kwargs: Any,
)
datatrove's Trafilatura extractor (same arguments and defaults) on pyroid.trafilatura.
doc.text becomes trafilatura.extract(doc.text, favor_precision=favour_precision,
include_comments=False, deduplicate=deduplicate, **kwargs), byte for byte, computed in
this process batch_size documents at a time; the same documents are forwarded and
dropped, with the same statistics (total, extracted, forwarded, dropped,
doc_len). Errors count as datatrove counts them: a page whose extraction raises is
broken_process (it kills datatrove's child process, and the next child starts with an
empty dedup cache, so the rest of the task does here too), and arguments that make
trafilatura.extract("") raise (datatrove's warm-up call) make every page timeout.
Trafilatura itself is not needed.
Extra keyword-only arguments: compat, the trafilatura release to reproduce (None:
the installed trafilatura's version when pyroid reproduces it, which includes the 1.11.0 that
datatrove[processing] installs, or pyroid's default, 2.3.1, when trafilatura is not
installed; for any other installed version the documents go to datatrove's own step, with a
one-time pyroid.FallbackWarning), batch_size and threads.
What differs: there is no child process and no timeout (pages datatrove gives up on
after timeout seconds, counting timeout and restarting its dedup cache, get their
text here). With deduplicate=True the dedup cache is a fresh
pyroid.trafilatura.LRUCache per run, i.e. per task, as datatrove's per-task
child process has a fresh one. Time is tracked per batch.
WarcReader ¶
datatrove's WarcReader (same arguments and defaults) on pyroid.warc.iter_documents.
Each file is read as datatrove's read_file reads it, with warcio's parsing, the
decompression and process_record in Rust: the same documents (text, id,
metadata: url, date, file_path and default_metadata), the same records
skipped, and the same exceptions, warnings and stderr messages at the same document, for
gzip and plain files, on the local filesystem and on any other fsspec filesystem
(ArchiveLoadFailed is warcio's class, as datatrove raises it, when warcio is installed).
limit, skip, sharding and the statistics are datatrove's own code. The page text is
decoded as datatrove decodes it: UTF-8, else cchardet's guess; python-magic is called
only for records without WARC-Identified-Payload-Type. warcio itself is not needed.
How a file is read: a local .gz (compression="infer" or "gzip") or plain file is
opened and read in Rust without the GIL, with the gzip layer reproducing CPython's
gzip.GzipFile (datatrove reads through it); on another filesystem the file is opened
with data_folder.open(path, "rb", compression=None) and that same emulation runs over it.
Any other compression ("zstd", an inferred .bz2, ...), and .gz when fsspec's
gzip opener is not CPython's (python-isal installed), use datatrove's own
data_folder.open(..., compression=...), read as is.
What differs: time is tracked per document, including the reading of the records before
it, plus once per file for the records after the last document (datatrove times each
record's process_record).
pyroid.integrations.nemo_curator¶
Each class subclasses NeMo Curator 1.4.0's class of the same name and takes its arguments; the
stages also take the keyword threads.
Drop-in NeMo Curator text stages backed by pyroid.
Change the imports and leave the pipeline as it is:
# from nemo_curator.stages.text.filters import ScoreFilter
# from nemo_curator.stages.text.filters.heuristic import WordCountFilter
from pyroid.integrations.nemo_curator import ScoreFilter, WordCountFilter
pipeline.add_stage(ScoreFilter(WordCountFilter(min_words=80)))
The stages ScoreFilter, Score, Filter and Modify subclass
NeMo's and take the same arguments, plus a keyword threads (pyroid's threads=, default 1:
NeMo already runs one worker per CPU). They score or modify a whole batch in one pyroid call instead
of calling score_document / modify_document once per row through pandas.Series.apply,
and they do this for NeMo's own filter and modifier objects too, so a pipeline built from NeMo's
YAML configs only needs its ScoreFilter / Modify targets changed (or
pyroid.patch with "nemo_curator").
The filters and modifiers here subclass NeMo's classes of the same name and take the same
arguments. Their score_document / modify_document run pyroid's kernel for one document, so
they are faster inside NeMo's own stages as well:
- the heuristic filters of
nemo_curator.stages.text.filters.heuristic(string and repetition); FastTextLangIdandFastTextQualityFilter, which load their model withpyroid.fasttext(fasttext, which needs a C++ compiler to install, is not needed);UnicodeReformatter(pyroid.ftfy; ftfy is not needed), and the string modifiersBoilerPlateStringModifier,MarkdownRemover,NewlineNormalizer,QuotationRemover,UrlRemover,LineRemover,SlicerandFastTextLabelModifier.
The output is the same as with NeMo's code: the same scores (same values and pandas dtypes), the
same kept rows, the same text, and the same exception where NeMo raises one (for a document NeMo's
code would fail on, the stage runs NeMo's code, which raises). Anything pyroid does not port runs
NeMo's own code, with a one-time pyroid.FallbackWarning where a setting caused it: a
custom url_regex, Japanese word splitting (MeCab), precomputed sentences / paragraphs
/ ngrams on a filter, subclasses of these classes, and multi-input modifiers.
RepeatingTopNGramsFilter breaks ties between equally frequent n-grams the way NeMo does: by
the order of a Python set, which depends on the process's string hash seed. NeMo's own scores
for such documents therefore change from run to run unless PYTHONHASHSEED is fixed; pyroid
gives the score NeMo gives in the same process.
Needs NeMo Curator 1.4.0 (Python 3.11 to 3.13); pyroid.nemo has the same kernels without it.
ScoreFilter
dataclass
¶
ScoreFilter(*, threads: int | None = 1)
NeMo's ScoreFilter, scoring and filtering each batch in one pyroid call.
The same arguments as NeMo's, plus threads (keyword; default 1).
Filter
dataclass
¶
Filter(*, threads: int | None = 1)
NeMo's Filter, deciding each batch with array comparisons.
The same arguments as NeMo's, plus threads (keyword; default 1; unused, for symmetry).
FastTextLangId ¶
FastTextLangId(
model_path: str | None = None,
min_langid_score: float = 0.3,
lang: str | None = None,
)
NeMo's FastTextLangId, with the model loaded by pyroid.fasttext.
The same arguments and the same score strings (str([score, lang])) as NeMo's.
FastTextQualityFilter ¶
FastTextQualityFilter(
model_path: str | None = None,
label: str = "__label__hq",
alpha: float = 3,
seed: int = 42,
)
NeMo's FastTextQualityFilter, with the model loaded by pyroid.fasttext.
The same arguments, scores and draws: keep_document draws from numpy's global random state
(seeded once, when the filter is created), as NeMo does, also in pyroid's batched stages.
UnicodeReformatter ¶
UnicodeReformatter(
config: Any = None,
unescape_html: str | bool = "auto",
remove_terminal_escapes: bool = True,
fix_encoding: bool = True,
restore_byte_a0: bool = True,
replace_lossy_sequences: bool = True,
decode_inconsistent_utf8: bool = True,
fix_c1_controls: bool = True,
fix_latin_ligatures: bool = False,
fix_character_width: bool = False,
uncurl_quotes: bool = False,
fix_line_breaks: bool = False,
fix_surrogates: bool = True,
remove_control_chars: bool = True,
normalization: Literal["NFC", "NFD", "NFKC", "NFKD"]
| None = None,
max_decode_length: int = 1000000,
explain: bool = True,
)
NeMo's UnicodeReformatter through pyroid.ftfy (ftfy itself is not needed).
The same arguments and defaults as NeMo's, which differ from ftfy's: no Unicode normalization, and ligatures, character widths, curly quotes and line breaks are left alone.
pyroid.integrations.hf¶
Dataset.map and Dataset.filter helpers for Hugging Face datasets.
Each helper returns a callable for batched=True:
import datasets
from pyroid.integrations import hf
ds = datasets.Dataset.from_parquet("fineweb.parquet").with_format("arrow")
ds = ds.map(hf.fix_text(), batched=True) # ftfy
ds = ds.map(hf.langid(), batched=True) # + language, language_score
ds = ds.filter(hf.quality_mask(), batched=True) # the Gopher, C4 and FineWeb filters
res = hf.fineweb(datasets.Dataset.from_parquet("fineweb.parquet")) # the whole recipe
res.dataset.to_parquet("clean.parquet")
print(res.stats)
The callables take both batch kinds datasets hands out:
- Arrow batches (
ds.with_format("arrow"), the zero-copy path). The callable gets apyarrow.Tablewhose text column pyroid reads in place, without building a Pythonstrfor it. A map callable returns apyarrow.Tablewith every column of the batch.datasetsreplaces the batch with it, soremove_columns=has no effect: call.remove_columns()on the dataset. A filter callable returns a booleanpyarrow.ChunkedArray. - Python batches (the default format). The callable gets the dict of lists
and returns a dict of the new or changed columns, which
datasetsmerges into the batch, or alist[bool]forfilter.Nonetexts stayNoneand are dropped by filters.
Arrow nulls are dropped by filters too (a null mask entry drops the row).
Don't pass input_columns=: the callables read their column by name. They
work the same in a streaming IterableDataset's map and filter,
except that an Arrow-format IterableDataset.filter needs datasets 5 (older
versions reject it whatever the function). fineweb needs a
Dataset, because MinHash reads the survivors back. Tested with datasets
3.6, 4.8 and 5.1.
The callables pickle by their settings, so datasets can fingerprint them
(cached results are reused) and ship them to num_proc worker processes,
each of which loads the fastText model itself. A loaded
pyroid.fasttext.Model can't be pickled: pass its path for both.
Needs pyarrow, numpy and datasets (pip install 'pyroid[hf]').
LidModel
module-attribute
¶
LidModel: TypeAlias = (
"str | os.PathLike[str] | pyroid.fasttext.Model | None"
)
ANNOTATION_COLUMNS
module-attribute
¶
ANNOTATION_COLUMNS = (
"keep",
"reason",
"language",
"language_score",
)
Stats
dataclass
¶
Stats(
docs: int,
kept: int,
dropped: dict[str, int] = dict(),
reasons: dict[str, int] = dict(),
seconds: dict[str, float] = dict(),
)
What fineweb did.
Attributes:
-
docs(int) –Documents in.
-
kept(int) –Documents out.
-
dropped(dict[str, int]) –Documents dropped per stage, in chain order (
lid, the four filters,minhash;nulltoo if there were null texts). -
reasons(dict[str, int]) –Documents dropped per reason (
"gopher_quality:gopher_short_doc","minhash", ...), most frequent first. -
seconds(dict[str, float]) –Wall time:
annotate(theDataset.mapof ftfy, LID and the filters),dedup(MinHash over the survivors),total(including building the output datasets).
FineWebResult
dataclass
¶
FineWebResult(
dataset: Dataset, annotated: Dataset, stats: Stats
)
What fineweb returns.
Attributes:
-
dataset(Dataset) –The kept documents, in order: cleaned text, the input's other columns, and
language/language_score. -
annotated(Dataset) –Every document, with the text as it left the chain and the columns
keep(the final decision) andreason(Noneif kept;"minhash"for a near-duplicate; seeannotate). -
stats(Stats) –Counts and timings.
fix_text ¶
fix_text(
column: str = "text",
config: TextFixerConfig | None = None,
*,
threads: int | None = None,
**kwargs: Any,
) -> Callable[[Batch], Any]
A Dataset.map(batched=True) function that runs ftfy.fix_text on a column.
Parameters:
-
column(str, default:'text') –The text column, replaced by its fixed text.
-
config(TextFixerConfig | None, default:None) –As for
pyroid.ftfy.fix_text_batch(None: ftfy's defaults). -
threads(int | None, default:None) –pyroid's
threads=(None: the whole thread pool). -
**kwargs(Any, default:{}) –Config fields, as for
pyroid.ftfy.fix_text_batch.
Returns:
langid ¶
langid(
lid_model: LidModel = None,
column: str = "text",
*,
k: int = 1,
threads: int | None = None,
language_column: str = "language",
score_column: str = "language_score",
) -> Callable[[Batch], Any]
A Dataset.map(batched=True) function that adds fastText language ID columns.
Each document is predicted as one line (newlines become spaces, as in
datatrove's FastTextLID).
Parameters:
-
lid_model(LidModel, default:None) –A path to a fastText LID model (
lid.176.bin), a loadedpyroid.fasttext.Model(in-process only, see the module docstring), orNoneforpyroid.integrations.recipes.lid_model_path($PYROID_LID_MODEL, else downloaded once into a cache). -
column(str, default:'text') –The text column.
-
k(int, default:1) –Number of labels;
1adds a string and a float column, any other value (-1: all labels) adds list columns, best first. -
threads(int | None, default:None) –pyroid's
threads=. -
language_column(str, default:'language') –Name of the label column (labels without
__label__). -
score_column(str, default:'language_score') –Name of the probability column (float64).
Returns:
quality_mask ¶
quality_mask(
column: str = "text",
filters: Sequence[Any] | None = None,
*,
threads: int | None = None,
) -> Callable[[Batch], Any]
A Dataset.filter(batched=True) function: keep what passes the quality filters.
The filters run in order, as datatrove's pipeline does
(pyroid.filters.apply). Only the keep decision is used:
C4Quality's line removal is not applied to the text (use
annotate or fineweb for the text as it leaves the chain).
Parameters:
-
column(str, default:'text') –The text column.
-
filters(Sequence[Any] | None, default:None) –pyroid.filtersconfigs or datatrove filter instances, in order;Noneis FineWeb's set (GopherRepetition, GopherQuality, C4Quality(filter_no_terminal_punct=False), FineWebQuality). -
threads(int | None, default:None) –pyroid's
threads=.
Returns:
keep_mask ¶
annotate ¶
annotate(
column: str = "text",
*,
lid_model: LidModel = None,
threads: int | None = None,
) -> Callable[[Batch], Any]
A Dataset.map(batched=True) function that runs the FineWeb chain but the dedup.
Per batch, pyroid.integrations.recipes.FineWeb.annotate: ftfy ->
LID (en, p > 0.65) -> GopherRepetition -> GopherQuality -> C4Quality ->
FineWebQuality. column becomes the text as it leaves the chain (for a
dropped document, the text the dropping stage saw) and these columns are
set: keep (bool), reason (None if kept, else "lid",
"<stage>:<datatrove reason>" or "null"), language and
language_score. Drop with ds.filter(hf.keep_mask(), batched=True);
MinHash dedup needs the whole corpus: see fineweb.
Parameters:
-
column(str, default:'text') –The text column.
-
lid_model(LidModel, default:None) –As for
langid. -
threads(int | None, default:None) –pyroid's
threads=.
Returns:
fineweb ¶
fineweb(
ds: Dataset,
column: str = "text",
*,
lid_model: LidModel = None,
threads: int | None = None,
batch_size: int = 1000,
num_proc: int | None = None,
**map_kwargs: Any,
) -> FineWebResult
Run the FineWeb recipe on a dataset, MinHash dedup included.
ds.map(annotate(...), batched=True)on Arrow batches: ftfy, LID and the four quality filters (annotate).- MinHash (datatrove's FineWeb settings) over the survivors, read back in
dataset order: each cluster of near-duplicates keeps one document, as in
datatrove, and the others are dropped (
reason="minhash"). This step always runs in this process over the whole dataset, so it is global and its result does not depend onbatch_sizeornum_proc.
On the same documents, the result equals datatrove's FineWeb pipeline
(bench/chain.py's digest).
Parameters:
-
ds(Dataset) –A
datasets.Dataset(any format; the results get the same format type). -
column(str, default:'text') –The text column.
-
lid_model(LidModel, default:None) –As for
langid. -
threads(int | None, default:None) –pyroid's
threads=for every stage. Withnum_procworker processes, aboutos.cpu_count() // num_procavoids oversubscription. -
batch_size(int, default:1000) –Documents per
Dataset.mapbatch (and per dedup read). -
num_proc(int | None, default:None) –Dataset.map's worker processes for step 1 (each loads the fastText model). pyroid already runs every batch on all cores, so this rarely helps;lid_modelmust be a path (orNone). -
**map_kwargs(Any, default:{}) –Passed to step 1's
Dataset.map(keep_in_memory,load_from_cache_file,cache_file_name,desc, ...).
Returns:
Raises:
-
ValueError–dsis empty, ornum_proc > 1with a loadedpyroid.fasttext.Model.
pyroid.integrations.recipes¶
Cleaning recipes: the FineWeb chain, natively in pyroid.
FineWeb is the chain the pyroid benchmark gates against datatrove
(bench/chain.py), over Arrow batches, as a reusable object:
ftfy.fix_text (ftfy's default config)
-> LanguageFilter(languages=["en"], language_threshold=0.65), fastText lid.176.bin
-> GopherRepetitionFilter() -> GopherQualityFilter()
-> C4QualityFilter(filter_no_terminal_punct=False) -> FineWebQualityFilter()
-> MinHash dedup (sha1/64, 14 buckets x 8 hashes, word 5-grams) over the survivors, in order
Per batch, FineWeb.annotate runs every stage but the dedup and returns
Arrow columns (no Python str is built for the texts). Dedup is a property
of the whole corpus: feed the survivors of each batch, in order, to the index
from FineWeb.new_dedup and read its keep mask at the end. This is what
pyroid clean --recipe fineweb does.
FineWebWarc is datatrove's examples/fineweb.py from WARC files (pyroid clean
--recipe fineweb-warc): pyroid.warc reads the pages, then the URL filter,
TaskExtractor (datatrove's Trafilatura step), FineWeb without ftfy, MinHash and
PII replacement.
Needs pyarrow and numpy (pip install 'pyroid[cli]').
LID_176_URL
module-attribute
¶
LID_176_URL = "https://dl.fbaipublicfiles.com/fasttext/supervised-models/lid.176.bin"
LID_176_MIRRORS
module-attribute
¶
LID_176_MIRRORS: tuple[str, ...] = (
"https://huggingface.co/julien-c/fasttext-language-id/resolve/main/lid.176.bin",
)
LID_176_SHA256
module-attribute
¶
LID_176_SHA256 = "7e69ec5451bc261cc7844e49e4792a85d7f09c06789ec800fc4a44aec362764e"
MINHASH_CONFIG
module-attribute
¶
MINHASH_CONFIG: dict[str, Any] = {
"n_grams": 5,
"num_buckets": 14,
"hashes_per_bucket": 8,
"seed": 1,
"hash_fc": "sha1",
"precision": 64,
}
FINEWEB_TRAFILATURA
module-attribute
¶
FINEWEB_TRAFILATURA: dict[str, Any] = {
"compat": "1.11.0",
"favor_precision": True,
"include_comments": False,
}
Annotation
dataclass
¶
Annotation(
text: ChunkedArray,
keep: ChunkedArray,
reason: ChunkedArray,
language: ChunkedArray,
language_score: ChunkedArray,
)
What FineWeb.annotate returns: one value per input document.
Attributes:
-
text(ChunkedArray) –The text as it leaves the chain (after ftfy and C4's line removal); for a dropped document, the text the dropping stage saw.
-
keep(ChunkedArray) –True if the document survived every stage but the dedup.
-
reason(ChunkedArray) –Noneif kept, else"lid"or"<stage>:<datatrove reason>"(e.g."gopher_quality:gopher_short_doc");"null"for a null text. -
language(ChunkedArray) –fastText's top label, without
__label__(datatrove'slanguagemetadata). -
language_score(ChunkedArray) –Its probability (datatrove's
language_score).
FineWeb ¶
FineWeb(
*,
lid_model: Any = None,
languages: Sequence[str] | None = ("en",),
language_threshold: float = 0.65,
ftfy_config: Any = None,
filters: Sequence[Any] | None = None,
minhash: Any = MINHASH_CONFIG,
threads: int | None = None,
)
The FineWeb chain (see the module docstring), stage for stage as datatrove.
Parameters:
-
lid_model(Any, default:None) –A
pyroid.fasttext.Model, a path tolid.176.bin, orNoneforlid_model_path's default (loaded on first use). -
languages(Sequence[str] | None, default:('en',)) –Languages to keep (
None: any). -
language_threshold(float, default:0.65) –datatrove
LanguageFilter'slanguage_threshold. -
ftfy_config(Any, default:None) –A
TextFixerConfig(pyroid.ftfy's or ftfy's);Noneis ftfy's default config, as in the benchmark chain.Falseskips ftfy. -
filters(Sequence[Any] | None, default:None) –The quality filters, in order (
pyroid.filtersconfigs or datatrove filter instances);Noneis the FineWeb set above. -
minhash(Any, default:MINHASH_CONFIG) –MinHash settings for
new_dedup(a dict ofMinhashConfigfields, or aMinhashConfig);Nonedisables dedup. -
threads(int | None, default:None) –Default
threads=for every stage (None: the whole pool).
Language decisions are datatrove's: keep iff some requested language's probability is above the threshold. Above 0.501 that can only be the top label (fastText's probabilities sum to at most 1 + 176e-5), so the model is asked for its top label only; at lower thresholds it is asked for all labels.
default_filters
staticmethod
¶
FineWeb's quality filters, in datatrove examples/fineweb.py's order.
new_dedup ¶
new_dedup(
*, threads: int | None = None
) -> MinHashDedup | None
A fresh MinHash index for this recipe's survivors (None if dedup is off).
annotate ¶
annotate(
texts: Any, *, threads: int | None = None
) -> Annotation
Run ftfy, LID and the filters on one batch (see Annotation).
Parameters:
FineWebWarc ¶
FineWebWarc(
*,
lid_model: Any = None,
threads: int | None = None,
url_filter: Any = None,
trafilatura: dict[str, Any] | None = None,
)
datatrove examples/fineweb.py from WARC files, stage for stage as datatrove 0.10.1.
Per WARC file (one datatrove task each):
WarcReader (pyroid.warc.iter_documents: warcio's parsing, datatrove's process_record)
-> URLFilter() -> Trafilatura(favour_precision=True), trafilatura 1.11.0
-> LanguageFilter(languages=["en"], language_threshold=0.65)
-> GopherRepetitionFilter() -> GopherQualityFilter()
-> C4QualityFilter(filter_no_terminal_punct=False) -> FineWebQualityFilter()
then MinHash dedup (MINHASH_CONFIG) over the survivors of every file, in file
order, and PIIFormatter() on what is left, a fresh rotation per file. documents,
annotate (with a new_task extractor per file), new_dedup and
new_pii are the pieces; pyroid clean --recipe fineweb-warc runs them.
Parameters:
-
lid_model(Any, default:None) –As
FineWeb's. -
threads(int | None, default:None) –Default
threads=for every stage (None: the whole pool). -
url_filter(Any, default:None) –A
pyroid.filters.URLFilter(default:URLFilter(), built at first use: it needs tldextract and datatrove's block lists). -
trafilatura(dict[str, Any] | None, default:None) –trafilatura.extractarguments overFINEWEB_TRAFILATURA.
documents
staticmethod
¶
documents(
source: Any,
*,
compression: Literal["infer", "gzip"] | None = "infer",
limit: int = -1,
) -> Iterator[Document]
The documents of one WARC file, as datatrove's WarcReader makes them.
{"text", "id", "url", "date"} per document, from pyroid.warc.iter_documents;
documents with an empty text are skipped and at most limit are read (-1: all),
as WarcReader does within one task.
new_task ¶
new_task() -> TaskExtractor
The Trafilatura step of one task (one per WARC file, in the file's order).
new_dedup ¶
new_dedup(
*, threads: int | None = None
) -> MinHashDedup | None
A fresh MinHash index for the survivors of every file, in file order.
new_pii
staticmethod
¶
new_pii() -> PIIReplacer
PIIFormatter() for one file (one task): its replacement rotation starts at 0.
annotate ¶
annotate(
docs: Sequence[Mapping[str, Any]],
task: TaskExtractor,
*,
threads: int | None = None,
) -> WarcAnnotation
WarcAnnotation
dataclass
¶
WarcAnnotation(
text: list[str],
keep: list[bool],
reason: list[str | None],
language: list[str | None],
language_score: list[float | None],
)
What FineWebWarc.annotate returns: one value per input document.
Attributes:
-
text(list[str]) –The text as it leaves the chain; for a dropped document, the text the dropping stage saw (the HTML for the URL filter and Trafilatura).
-
keep(list[bool]) –True if the document survived every stage but MinHash.
-
reason(list[str | None]) –Noneif kept, else"url_filter:<reason>","trafilatura"(no text extracted),"trafilatura:broken_process"(the extraction raised),"lid"or"<stage>:<datatrove reason>". -
language(list[str | None]) –fastText's top label (
Nonebefore the LID stage). -
language_score(list[float | None]) –Its probability.
TaskExtractor ¶
datatrove's Trafilatura step over the pages of one task, on pyroid.trafilatura.
Make one per datatrove task (in FineWeb, one per WARC file): it starts with an empty dedup
cache, as the step's child process does. Called on a list of HTML pages, it returns for each
the extracted text (None or "" when there is none: the step drops those), or the
exception its extraction raised. datatrove's child process dies on such a page (the step
counts broken_process and drops it) and the next child starts with an empty cache, so the
cache is emptied after it here too. Pages are extracted in order: the dedup cache depends on
it.
Parameters:
-
threads(int | None, default:None) –threads=ofpyroid.trafilatura.extract_batch(None: the whole pool; with deduplication the pages are still extracted in order; threads parse ahead). -
**kwargs(Any, default:{}) –trafilatura.extractarguments overFINEWEB_TRAFILATURA. Withcompat="1.11.0"the dedup cache ispyroid.trafilatura.LRU_TEST, as trafilatura 1.11.0 has only its module-level cache; with"2.3.1", anLRUCacheof the task.
lid_model_path ¶
The fastText lid.176.bin file to use.
Parameters:
-
path(str | PathLike[str] | None, default:None) –An explicit model file; returned as is.
-
download(bool, default:True) –When neither
pathnor$PYROID_LID_MODELis set and the cached copy is missing, download it fromLID_176_URL(else fromLID_176_MIRRORS) into the cache ($PYROID_CACHE_DIR, else$XDG_CACHE_HOME/pyroid, else~/.cache/pyroid) and check its sha256.
Returns:
-
str–The model path.
Raises:
-
FileNotFoundError–No model, and
downloadis false. -
OSError–The download failed or its sha256 is wrong.