team-qbits / training /REPRODUCE.md
ovinduG's picture
Upload folder using huggingface_hub
e55c8a4 verified
|
Raw History Blame Contribute Delete
8.09 kB
# Reproducing our SinhalaMMLU Shared Task submission
Team: QBitsLK Β· ICTer 2026 SinhalaMMLU Shared Task
Everything below runs with no human in the loop. Inference is deterministic: no sampling, no
generation, fixed seeds, and the prediction is an argmax over summed log-softmax scores.
## 1. Quick start β€” reproduce the submitted predictions
```bash
pip install unsloth
pip install --no-deps transformers==5.5.0 "tokenizers>=0.22.0,<=0.23.0"
pip install "huggingface_hub>=1.5.0,<2.0"
pip install --no-deps --upgrade timm
pip install hf_transfer
python check_env.py # verifies the whole stack; exits non-zero on a problem
bash run_inference.sh /path/to/test_root ./out
```
`test_root` is the directory holding `easy/`, `medium/` and `hard/`. The script writes
`out/predictions.jsonl` and `out/predictions.zip` (with `predictions.jsonl` at the archive root).
Hardware used for every reported run: one **NVIDIA A40, 48 GB**, driver 595.91, CUDA 13.2.
Inference over the 2,238-question test set takes about **18 minutes** and peaks around 27 GB of VRAM.
## 2. The submitted system in one line
| | |
|---|---|
| model | `QBitsLK/gemma4-e4b-sinhala-mcq-cpt-v3` (merged 16-bit) |
| parameters | **7,996,156,448** β€” the entire inference pipeline, one model, ≀ 8B |
| base | `QBitsLK/gemma4-e4b-sinhala-cpt` (Gemma 4 E4B + Sinhala continued pretraining) |
| adapter | LoRA r=32, Ξ±=32, merged into the base; merging adds no parameters |
| scoring | per-option log-likelihood, 5-prompt ensemble, argmax |
| prompt variants | `si_intro si_nointro en_intro si_rot1 si_rot2` |
| seed | 3407 |
The parameter count can be checked without downloading the weights:
```bash
python -c "
from huggingface_hub import get_safetensors_metadata
t = sum(get_safetensors_metadata('QBitsLK/gemma4-e4b-sinhala-mcq-cpt-v3').parameter_count.values())
print(f'{t:,}', 'OK, <= 8B' if t <= 8_000_000_000 else 'OVER 8B')"
```
**The adapter is merged on purpose.** Base + adapter loaded separately is 8,069,556,768 parameters,
which exceeds the cap; merging folds the LoRA into the existing weight matrices and returns the model
to 7,996,156,448.
## 3. Results reproduced by this package
Codabench, hidden test set (2,238 questions: easy 642, medium 1,111, hard 485):
| run | easy | medium | hard | **macro** |
|---|---|---|---|---|
| **submitted** β€” CPT + v3 data, 5-prompt ensemble | 58.26 | 62.02 | **52.58** | **57.62** |
| ablation β€” base Gemma 4 + v3 data, 5-prompt ensemble | 57.01 | 63.10 | 51.34 | 57.15 |
| ablation β€” base Gemma 4 + v3 data, single prompt | β€” | β€” | β€” | 55.66 |
To reproduce the first ablation, change `MODEL` in `run_inference.sh` to
`QBitsLK/gemma4-e4b-sinhala-mcq-base-v3b`. For the second, set `VARIANTS="si_intro"`.
## 4. Full reproduction from scratch
### 4.1 Rebuild the training data (CPU, a few minutes)
```bash
bash run_data_pipeline.sh
```
Requires the raw extraction files in `./data` (see section 5). Expected output: **7,872** questions
after deduplication, split into **7,151 train β†’ 21,235 examples** and **721 validation**.
The public `TEST/easy` split is downloaded by this script and used **only** as a decontamination
blocklist. It removed **26 questions** that our past-paper extractions shared with the released test
data. It is never used for training, checkpoint selection, prompt design or hyperparameter choice.
### 4.2 Re-run the fine-tune (1 GPU, 88 minutes)
```bash
bash run_training.sh /workspace/outputs/mcq_sft_cpt_v3
```
This downloads the published SFT files from
`https://huggingface.co/datasets/QBitsLK/sinhala-mcq-sft-v3`, so section 4.1 is optional if you only
want to reproduce training.
Expected validation curve (721 held-out questions, accuracy):
| step | 0 | 150 | 300 | 450 | 600 | 664 | 750 | 900 | 1050 | 1200 | 1328 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| acc | .2316 | .5520 | .5395 | .5728 | .5506 | .5562 | .5867 | .5645 | .5742 | .5825 | **.5811** |
**The submitted checkpoint is `final_adapter` (step 1328), not `best_adapter`.** Rationale in
`SYSTEM_DESCRIPTION.md` Β§5.2: on a parallel run the validation-best checkpoint scored 0.7 points
*worse* on held-out test data than the final one, because our validation split shares the training
distribution and does not detect this failure mode.
### 4.3 Publish the merged model
```bash
export HF_TOKEN=...
python push_merged_to_hf.py \
--adapter /workspace/outputs/mcq_sft_cpt_v3/final_adapter \
--repo <your-namespace>/gemma4-e4b-sinhala-mcq-cpt-v3
```
Streams the merge directly to the Hub, so no 16 GB local copy is needed, and reads the parameter
count back for the ≀8B check.
## 5. Files
| file | purpose |
|---|---|
| `run_inference.sh` | reproduces the submitted predictions (the critical path) |
| `run_training.sh` | reproduces the fine-tune |
| `run_data_pipeline.sh` | rebuilds the SFT dataset from raw extractions |
| `make_predictions.py` | prompt builder, ensemble scorer, submission writer, format checks |
| `sft_gemma4_mcq.py` | LoRA SFT with answer-only loss; per-step candidate-scoring evaluation |
| `mcq_dataset.py` | extraction cleanup, validation, decontamination, splitting, prompt building |
| `add_new_data.py` | merges per-subject extractions into the dataset |
| `normalize_subjects.py` | aligns subject names with the official `metadata.subject_original` |
| `eval_sinhalammlu.py` | read-only analysis; also builds the decontamination blocklist |
| `push_merged_to_hf.py` | merges the adapter and publishes it |
| `check_env.py` | verifies the dependency stack before a run |
| `requirements.txt` | exact pins and install order |
**Not included:** the source examination PDFs. They are third-party past papers obtained from
publicly available sources (`pastpapers.wiki`, `e-thaksalawa.moe.gov.lk`), and we do not hold
redistribution rights. The derived question set β€” questions, options and answer keys β€” is published
at `QBitsLK/sinhala-mcq-sft-v3`, which is sufficient to reproduce training and inference. The raw
per-subject extraction files are included so `run_data_pipeline.sh` runs end-to-end.
## 6. Determinism
- No sampling anywhere; `model.generate` is never called on the submission path.
- `random.seed(3407)` and `torch.manual_seed(3407)`.
- The prediction is `argmax` over log-softmax sums; ties resolve to the lowest option index.
- Prompt variants are explicit in `run_inference.sh`; changing them changes the predictions.
- `make_predictions.py` refuses to write the zip unless the line count equals the question count,
every line carries exactly `{q_no, subject, category, level, answer}`, `answer` is a `"1"`–`"5"`
string, and every `level` is `easy`/`medium`/`hard`.
One deliberate deviation from the organisers' reference code: `evaluate.py` hard-codes the *hard*
instruction as "1, 2, 3, 4, 5" because the original SinhalaMMLU hard split had five options.
SinhalaMMLU 2.0 drops one incorrect option, so hard questions now have four. `fit_numbers()` in
`make_predictions.py` rewrites the number list to match the actual option count (verified: 0 of 2,238
prompts disagree). Without this, all 485 hard questions would be asked to choose from five options
when only four are shown.
## 7. Citation
This work evaluates on the SinhalaMMLU benchmark:
```bibtex
@inproceedings{pramodya-etal-2025-sinhalammlu,
title = "SinhalaMMLU: A Comprehensive Benchmark for Evaluating Multitask Language Understanding in Sinhala",
author = "Pramodya, Ashmari and Nelki, Nirasha and Shalinda, Heshan and Liyanage, Chamila and
Sakai, Yusuke and Pushpananda, Randil and Weerasinghe, Ruvan and
Kamigaito, Hidetaka and Watanabe, Taro",
booktitle = "Proceedings of the 2025 Conference on Empirical Methods in Natural Language Processing",
year = "2025",
pages = "32943--32961",
url = "https://aclanthology.org/2025.emnlp-main.1673/"
}
```
Verify this against the BibTeX on <https://sinhalammlu.ucsc.cmb.ac.lk/task.html> before submitting;
it is reproduced here from that page and the arXiv preprint (arXiv:2509.03162).