Tatva Data Factory v1¶
The reusable dataset-cleansing engine for the Tatva from-scratch lane — and
for every future corpus we train on. It turns raw text shards into a
provenance-receipted corpus build: every accepted document is tracked by
content hash, every shard by SHA-256 + source/license/date, and the whole
build collapses into one citable corpus_build_hash. Training-side receipts,
the same discipline our serving side already has.
Design follows §5 ("the cheapest useless token is the one you never train") and §14 (data metrics) of the DeepSeek learnings brief: dedup, quality scoring, language ID, domain classification, provenance and contamination checks — as infrastructure, not one-off scripts.
Stages¶
| # | Stage | Module | Coverage |
|---|---|---|---|
| 1 | Ingest + provenance ledger | registry.py |
every shard, before its file is opened |
| 2 | Language ID (fastText lid.176, script fallback) | lid.py |
sampled |
| 3 | Dedup — exact (64-bit content hash) | shards.py + dedup.py |
full corpus |
| 3b | Dedup — near (MinHash 32-perm, LSH 8×4, verified) | dedup.py |
sampled |
| 4 | Quality scoring v1 (heuristics, documented thresholds) | quality.py |
sampled |
| 5 | Domain classifier v1 (coding/bfsi/reasoning/indic/general) | domain.py |
sampled |
| 6 | Contamination vs our eval sets (8-gram, string-verified) | contamination.py + build_eval_set.py |
sampled |
| 7 | Build manifest + composition report | shuddhi merge |
— |
| 8 | PII scan/redact (email, phone, Aadhaar, PAN, Luhn-checked cards, IP) | pii.py |
sampled; full-doc on build |
| 9 | Perplexity proxy (per-language char-trigram LM, bits/char) | ngram_lm.py |
sampled; per-doc on build |
| 10 | Applied-filter builds with chained hash | builder.py / shuddhi build |
full corpus |
| 11 | Applied near-dup (full-corpus MinHash/LSH, disk-backed, deterministic exemplar) | neardup.py |
full corpus |
| 12 | Toxicity screen (lexicon tier, external lists pluggable + sha-pinned) | toxicity.py |
per-doc on build |
| 13 | Extraction (HTML → shard text; trafilatura or fallback) | extract.py |
— |
v1.2 near-dup + toxicity flow:
for s in <shards>; do shuddhi neardup-sig --registry R --shard $s --sig-dir sigs/; done
shuddhi neardup-merge --registry R --run-dir out/ --sig-dir sigs/ --out neardup-drop.u64
shuddhi build ... --neardup-drop neardup-drop.u64 --toxicity
The provenance gate is mechanical. A shard is refused — before its file
is ever opened — if it is untagged, carries an unknown data_class, or is
tagged customer / customer-derived / evaluation-only. Customer data is
evaluation-only, never training: that rule is code with no override path
(registry.py::_refusal_reason, forbidden-class check first, unconditional
return). Suspiciously-named shards claiming a trainable class additionally
require a named human reviewer.
Full-pass vs sampled, never mixed silently. Shard SHA-256, document counts, and exact-dedup run over every byte. LID, quality, domain, near-dup, contamination and token ratios run on a deterministic index-stride sample and always report their exact coverage.
The build hash. corpus_build_hash = blake2b-256 over the sorted set of
unique 64-bit document hashes across accepted shards. Order-independent,
reproducible from the raw files, cheap to verify. Every training run that
consumes a corpus build cites this hash in its run ledger — that is the
contract.
Running¶
# validate the ledger (exit 2 if anything is refused)
shuddhi check --registry configs/reference-sangraha.json
# process one shard (all stages, one streaming pass)
shuddhi run --registry configs/reference-sangraha.json \
--shard sangraha_hin --out out/ \
--eval-set eval-set.jsonl --fasttext-model lid.176.ftz \
--tokenizer tokenizer.json
# merge all shard outputs into MANIFEST.json + COMPOSITION.md
shuddhi merge --registry configs/reference-sangraha.json --out out/
# train per-language perplexity-proxy LMs (once per corpus)
shuddhi train-lm --registry configs/reference-sangraha.json \
--shard sangraha_hin --lm-dir lms/
# applied-filter build over the MEASURED run (run -> merge -> build):
# exact-dedup keep-first, quality >= 0.5, per-language ppx cutoff at the
# measured p99, PII redaction, contamination drop. --emit none produces the
# hash-only manifest; --emit text also writes the filtered shards.
shuddhi build --registry configs/reference-sangraha.json \
--run-dir out/ --build-out build/ --lm-dir lms/ --ppx-percentile 99 \
--pii redact --eval-set eval-set.jsonl --emit none
The build hash. A build's filtered_build_hash is computed with the
same definition as the parent corpus_build_hash and recorded alongside the
parent hash + the sha256 of the exact filter config. Same raw files + same
config ⇒ same filtered hash, emission or not. Build also verifies integrity:
every document hash must exist in the measured run's set, so a shard that
changed after measurement fails the build loudly.
scripts/run-reference-corpus.sh drives all 15 Sangraha shards with 2 workers on the LLM VM
(CPU-only; the corpus lives there, so no Blob egress is spent).
Dependencies: numpy (required for dedup/merge); fasttext-predict (LID,
optional — falls back to Unicode-script ID and says so in the stats);
tokenizers (token ratios, optional). All pip wheels, no compilers.
Defaults: sample stride 50 (2% of documents), MinHash on every 4th sampled doc, token ratio on every 20th sampled doc up to 30 MB/shard. Tune per run; every stats file records the strides it ran with.
Tests¶
Outputs¶
out/<shard>.stats.json— per-shard receipts: provenance echo, sha256, full-pass counts, sampled metrics, timing.out/<shard>.hashes.u64— the shard's document content hashes (raw uint64), input to global dedup and to any future incremental build.out/MANIFEST.json— the corpus build manifest (the thing training runs cite).out/COMPOSITION.md— human-readable composition tables.
First production report: portfolio/DATA-FACTORY-V1.md.