Skip to content

Add data-pruning scaling-law experiment pipeline (Telugu) - #1

Open
vishnup22 wants to merge 11 commits into
mainfrom
data-pruning-scaling-law
Open

Add data-pruning scaling-law experiment pipeline (Telugu)#1
vishnup22 wants to merge 11 commits into
mainfrom
data-pruning-scaling-law

Conversation

@vishnup22

Copy link
Copy Markdown
Owner

Research question

Does pruning 30-50% of low-resource pretraining data using a small reference model preserve (or beat) 100%-baseline validation BPB — i.e. does it break standard power-law scaling? Motivated by Ari Morcos' NeurIPS finding (easy examples help when data is scarce, hard examples help at scale) and Datology's small-reference-model data curation work.

Full methodology: docs/pruning_experiment.md.

What this adds

  • pruning/score.py — streams a train split through a reference checkpoint (default: pulipakav-1/dravidian-gpt2-telugu) and writes per-line cross-entropy loss.
  • pruning/make_splits.py — partitions scored lines into 50% easy / hard / mid (40th-90th percentile Pareto core-set) / random subsets. Val/test splits are untouched so every variant is judged on identical held-out data.
  • evaluation/perplexity.py: score_lines() — new per-line (not batch-averaged) loss function that score.py depends on. Numerically verified against the existing eval_perplexity aggregate and confirmed padding-safe.
  • models/gpt2/train.py — new --variant / --eval_strategy / --eval_steps / --seed flags to train on a pruned split with step-level eval logging (eval_history: tokens_seen / eval_loss / bpb) for the tokens-vs-BPB curve. Omitting --variant reproduces prior behavior/filenames exactly (backward compatible).
  • pruning/evaluate_variants.py — BPB/perplexity + WikiANN NER F1 for every trained variant plus the existing 100% baseline (re-evaluated fresh from the HF Hub checkpoint for a fair final-number comparison; its training curve stays the existing coarse one — see docs for why).
  • analysis/plot_pruning_scaling.py + summarize_pruning.py — the deliverable tokens-vs-BPB plot and a results table.
  • scripts/run_pruning_experiment.sh (SLURM launcher), configs/experiments/telugu-pruning-scaling.yaml (experiment manifest), README pointers, matplotlib added as a dependency.

Verification

No GPU/corpus available in the dev environment, so:

  • All new/modified modules import cleanly.
  • make_splits.py run end-to-end on synthetic scores/text: clean 50/50 easy/hard partition, correct percentile thresholds for mid, no cross-split overlap, blank lines correctly excluded.
  • score_lines checked against a real (tiny) GPT-2 forward pass: matches eval_perplexity's aggregate loss exactly on single-example batches, and confirmed padding never leaks into another line's score.
  • build_eval_history unit-checked for correct tokens_seen/bpb computation and non-eval log entry filtering.

Full-scale scoring + training (4 variants + baseline eval) needs the SLURM cluster — run via scripts/run_pruning_experiment.sh, documented in docs/pruning_experiment.md.

Implements the core research question: does pruning 30-50% of low-resource
pretraining data with a small reference model preserve baseline BPB?

- evaluation/perplexity.py: score_lines() for per-line (not batch-averaged)
  cross-entropy scoring, verified against eval_perplexity and padding-safe.
- pruning/score.py: streams a train split through a reference checkpoint
  and writes per-line loss scores.
- pruning/make_splits.py: partitions a scored split into easy/hard/mid
  (Pareto core-set)/random 50% subsets; verified against synthetic data.
- models/gpt2/train.py: --variant/--eval_strategy/--eval_steps/--seed flags
  to train on pruned splits with step-level eval logging (eval_history:
  tokens_seen/eval_loss/bpb) for the tokens-vs-BPB curve; fully backward
  compatible when --variant is omitted.
- pruning/evaluate_variants.py: BPB/perplexity + WikiANN NER F1 for every
  variant plus the existing 100% baseline (re-evaluated from the HF Hub
  checkpoint for a fair final-number comparison).
- analysis/plot_pruning_scaling.py + summarize_pruning.py: the deliverable
  tokens-vs-BPB plot and results table.
- scripts/run_pruning_experiment.sh, configs/experiments/telugu-pruning-scaling.yaml,
  docs/pruning_experiment.md: SLURM launcher, experiment manifest, methodology writeup.
- matplotlib added as a dependency.
The launcher was configured for 1 GPU per variant, which (combined with
compute_total_steps scaling inversely with world_size) would have meant
~2x the baseline's step count per variant despite training on half the
data -- ~5.6 days/variant, ~22-24 days sequential, over the 14-day budget.

Switch to accelerate launch --num_processes 4 (matching train_gpt.sh and
the telugu_seed2 baseline), which brings each variant back to roughly
1.4 days (half the baseline's 67.1h on the same 4-GPU config), ~5.6 days
for all 4 sequentially. Bumped --gres to gpu:4 and reduced --time from
14 to 8 days accordingly.
…r.model

T5Tokenizer.from_pretrained() resolves vocab_files_names['vocab_file'],
which is hardcoded to 'spiece.model' -- but the Hub repo's raw SentencePiece
file is named 'tokenizer.model' (matching how models.gpt2.train saves it),
so auto-resolution silently returned None and crashed inside sentencepiece
with a non-obvious 'TypeError: not a string'.

Download the known filename explicitly via hf_hub_download, same pattern
train.py's load_tokenizer already uses for local checkpoints. Verified
against the real pulipakav-1/dravidian-gpt2-telugu repo: loads, vocab_size
32000, correct pad_token_id, encodes/decodes correctly.
--time was set to 8-00:00:00 but the cluster's actual cap is 7 days --
tight against the ~5.6-7 day estimate for the full pipeline, with no
margin for a mid-run timeout.

train_one() previously only skipped a variant if its result JSON already
existed, so a timeout mid-training would restart that variant from scratch
on resubmission, silently wasting however many days it had already run.
Now it resumes from the latest checkpoint (trainer.train(resume_from_checkpoint=True))
when output_dir has checkpoints but no finished result JSON, so the
pipeline is resumable at both the per-variant and mid-variant level --
just re-sbatch the same script if it times out.
conda's activation hook references unset variables internally, so -u
(nounset) makes 'conda activate telugu_llm' abort the script. -eo pipefail
still catches command failures and pipe failures; just not unset vars.
…odel loads

Passing dtype=/torch_dtype= directly into .from_pretrained() leaks that
kwarg into AutoConfig/PretrainedConfig's unused_kwargs on this transformers
version, gets stored as a stray non-JSON-serializable attribute on the
config, and crashes the first time the config is repr'd/logged (which
from_dict() does unconditionally when return_unused_kwargs is set) --
regardless of which model actually gets loaded.

Fixed all four call sites the same way: load without a dtype kwarg, then
.to(dtype=...) after. Verified against the real HF Hub checkpoint
(pulipakav-1/dravidian-gpt2-telugu) -- previously reproduced the exact
traceback reported from the cluster, now loads cleanly.

- evaluation/run_eval.py::load_model_and_tokenizer (the one that crashed)
- pruning/evaluate_variants.py::load_variant (same pattern, not yet exercised)
- evaluation/downstream.py::_load_seq_clf_model, run_wikiann_ner (same
  pattern with the old torch_dtype= name, would have hit this next in
  pruning.evaluate_variants' downstream eval step)
…ward pass)

Observed on the cluster: 198/~44,700 chunks in 6min at batch_size=8, which
projects to ~22.6h for the full scoring pass -- too much of the 7-day
budget for a step that's pure forward-pass inference. score_lines() is
already @torch.no_grad(), so there's no gradient/optimizer memory pressure
to justify a small batch size; bump it way up. Also bumped READ_CHUNK_LINES
512 -> 2048 to reduce per-chunk Python loop overhead.
…tegy

Newer transformers versions renamed evaluation_strategy -> eval_strategy.

Claude-Session: https://claude.ai/code/session_01F3trZ6zLPewuWpFRUPoisx
…lready done

Steps 1 (scoring, ~7.8h for te_train) and 2 (pruned split building) had no
idempotency check, so resubmitting the script after a crash further into
the pipeline (e.g. the just-fixed evaluation_strategy TypeError) would
silently redo the expensive scoring pass for no reason. Guard both on
their output files existing, matching the tokenizer step's existing
skip-if-present behavior and the resumability the script's header comment
already promised.

Claude-Session: https://claude.ai/code/session_01F3trZ6zLPewuWpFRUPoisx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant