This commit is contained in:
Jakub Schwarc 2026-11-12 18:27:37 +01:00
parent d20ba79d56
commit e2b20727e6
10 changed files with 744 additions and 671 deletions

View File

@ -1,150 +1,140 @@
# Slovenská Aya na Perunovi # Príprava a spustenie experimentov na Perune
Prvým modelom pre ďalšiu sériu je `ministral/Ministral-3b-instruct`. Po SFT s LoRA v LLaMA-Factory sa pôvodný a dotrénovaný model porovnajú na rovnakých úlohách SK-bench. `configs/ministral.yaml` pripína rovnakú východiskovú revíziu modelu, aká sa stiahla pre baseline. Formát chatu sa odvodí z tokenizéra modelu. Konfigurácie Mistral-SK a OLMo sú v balíku ako ďalšie možnosti. Postup pre dotrénovanie `ministral/Ministral-3b-instruct` na slovenskej časti Aya Collection metódou SFT s LoRA v LLaMA-Factory a Unslothe. Výsledky dokončeného behu sú v [dokumentácii experimentu](ministral_3b_aya/README.md). Príprava kontajnera aj vyhodnotenie v SK-bench sú v [návode pre SK-bench](SK_BENCH.md).
| Súbor | Úloha | ## 1. Pripojenie a cesty
| --- | --- |
| `scripts/prepare_data.py` | Jednorazová príprava spoločného datasetu. |
| `configs/ministral.yaml` | Model, revízia, LoRA a tréningové nastavenia. |
| `scripts/train.py` | Kontrola dát a spustenie tréningu s Unslothom. |
| `scripts/evaluate.py` | Loss, perplexita, slovný F1 a presná zhoda na spoločnom splite eval alebo test. |
## Jeden pevný dataset Po pripojení na VPN Peruna je potrebné prihlásiť sa na uzol `login01` cez SSH.
Príprava dát sa vykoná **raz**. Skript načíta slovenskú Aya, vynechá neplatné a prázdne riadky, odstráni normalizované duplicity a rozdelí dáta so seedom 42. Rovnaké zadania s rôznymi odpoveďami zostávajú v jednej množine. Eval aj test dostanú najviac 1 000 príkladov. Zvyšok patrí do train.
V `data/aya_slovak_fixed` sa uložia **úplné zadania a referenčné odpovede**. Pri zmene modelu sa použijú tie isté súbory a ten istý manifest. Tokenizácia prebehne až v LLaMA-Factory podľa modelovej konfigurácie. Pri `cutoff_len: 4096` sa prípadné orezanie týka spracovaných tokenov pre daný beh. Dĺžky a počet orezaných príkladov sa môžu medzi modelmi líšiť.
Manifest zaznamená zdroj, seed, počty a kontrolné súčty. `dataset_info.json` registruje `aya_train`, `aya_eval` a `aya_test`. Súbory `*.metadata.jsonl` uchovávajú pôvod príkladov v rovnakom poradí. Dočasná SQLite databáza zabezpečuje deduplikáciu a rozdelenie bez držania celého datasetu v RAM.
## 1. Príprava dát na CPU
Balík sa prenesie do `/mnt/project/perun26012578/perun`. Zdrojový Parquet je už v `/mnt/project/perun26012578/raw/slovak_train.parquet`. Pri príprave stačí Python s PyArrow, ktorý je dostupný aj v pripravenom kontajneri SK-bench.
Z prihlasovacieho uzla sa príprava odošle do `cpu_short`. Príkaz použije existujúci obraz a Python prostredie v `apphome`.
```bash ```bash
cd /mnt/project/perun26012578/perun ssh jascke919@login01.perun.tuke.sk
mkdir -p logs ```
export APPHOME=/mnt/home/jascke919/apphome
Príkazy predpokladajú, že priečinok `perun` z repozitára je v projektovom úložisku. Pri prvom prenose sa nasledujúci príkaz spúšťa v PowerShelli z koreňa lokálneho repozitára. Pri ďalších behoch sa používa existujúca kópia priečinka na Perune.
```powershell
scp -r .\perun jascke919@login01.perun.tuke.sk:/mnt/project/perun26012578/
```
Premenné je potrebné nastaviť na prihlasovacom uzle v relácii, z ktorej sa odosielajú joby.
```bash
export PROJECT=/mnt/project/perun26012578
export APPHOME=/mnt/home/jascke919/apphome
export TRAIN_ENV=/mnt/home/jascke919/aya_train_perun_managed
export PY="$TRAIN_ENV/bin/python"
cd "$PROJECT/perun"
mkdir -p logs
```
## 2. Príprava prostredia
Na prípravu dát je potrebný obraz `$APPHOME/cuda.sif` a Python 3.13 s PyArrow v `$APPHOME/venv`. Postup prvotnej inštalácie je v [príprave kontajnera SK-bench](SK_BENCH.md#1-príprava-kontajnera).
Tréning a Aya test používajú samostatné prostredie s Pythonom 3.11. Na Perune fungovalo prostredie vytvorené cez `uv --managed-python`. Obsahuje hlavičku `Python.h`, ktorú potrebuje Triton. V existujúcom prostredí `aya_train_perun_managed` sa závislosti overia cez `"$PY" -m pip check`. Pri novej inštalácii sa nasledujúce príkazy spúšťajú z priečinka `perun` s dostupným `uv`.
```bash
uv venv --managed-python --python 3.11 --seed "$TRAIN_ENV"
"$PY" -c 'import pathlib, sysconfig; p = pathlib.Path(sysconfig.get_path("include")) / "Python.h"; assert p.is_file(), p; print(p)'
"$PY" -m pip install torch==2.6.0 torchvision==0.21.0 torchaudio==2.6.0 xformers==0.0.29.post3 \
--index-url https://download.pytorch.org/whl/cu124
"$PY" -m pip install -r requirements/training.txt
"$PY" -m pip check
```
Pred prvým GPU jobom je potrebné stiahnuť základný model do cache Hugging Face. Príkaz vyžaduje prístup na internet a rovnaké nastavenie cache ako tréningový job.
```bash
"$PY" - <<'PYTHON'
from huggingface_hub import snapshot_download
snapshot_download(
"ministral/Ministral-3b-instruct",
revision="2c95908929198d6e69af8638f0dbbd9bc6b93f9e",
)
PYTHON
```
## 3. Príprava Aya dát na CPU
Zdrojový súbor je na Perune v `/mnt/project/perun26012578/raw/slovak_train.parquet`. Pripravené dáta sú v `data/aya_slovak_fixed`. Ak priečinok obsahuje pôvodný manifest a rozdelené dáta, pokračuje sa kontrolou tréningovej konfigurácie. Pri príprave od začiatku musí byť výstupný priečinok nový alebo prázdny.
Ak zdrojový Parquet na Perune chýba, po pripojení na VPN sa prenesie cez `scp`. Príkaz sa spúšťa z lokálneho priečinka so súborom.
```powershell
scp .\slovak_train.parquet jascke919@login01.perun.tuke.sk:/mnt/project/perun26012578/raw/
```
Kontrolný súčet pôvodného Parquetu sa overí cez `sha256sum "$PROJECT/raw/slovak_train.parquet"`. Očakávaný SHA-256 je `6a21c52b80549cbb8f9d13de981a2d8f65c273a17f651fd09c1efcc952821e82`.
```bash
sbatch --job-name=aya_prepare \ sbatch --job-name=aya_prepare \
--account=perun26012578 --partition=cpu_short \ --account=perun26012578 --partition=cpu_short \
--nodes=1 --ntasks=1 --cpus-per-task=4 --mem=24G \ --nodes=1 --ntasks=1 --cpus-per-task=4 --mem=24G \
--time=08:00:00 --output=logs/aya_prepare_%j.out \ --time=08:00:00 --output=logs/aya_prepare_%j.out \
--wrap='apptainer exec --home "$APPHOME" \ --wrap='apptainer exec --home "$APPHOME" \
--bind /mnt/project/perun26012578:/mnt/project/perun26012578 \ --bind "$PROJECT:$PROJECT" \
--pwd /mnt/project/perun26012578/perun "$APPHOME/cuda.sif" \ --pwd "$PROJECT/perun" "$APPHOME/cuda.sif" \
"$APPHOME/venv/bin/python" -u scripts/prepare_data.py \ "$APPHOME/venv/bin/python" -u scripts/prepare_data.py \
--local-parquet /mnt/project/perun26012578/raw/slovak_train.parquet \ --local-parquet "$PROJECT/raw/slovak_train.parquet" \
--output-dir data/aya_slovak_fixed' --output-dir data/aya_slovak_fixed --seed 42 --eval-size 1000 --test-size 1000'
``` ```
Po dokončení sa skutočné počty a kontrolné súčty nachádzajú v `data/aya_slovak_fixed/manifest.json`. Výstupný priečinok prípravy musí byť nový alebo prázdny. Pri vlastnom lokálnom JSONL slúži `--local-jsonl`. Bez lokálneho vstupu sa použije pripnutá revízia slovenskej Aya na Hugging Face. Príprava odstráni neplatné riadky a duplicitné páry. Rovnaké normalizované zadania drží v jednej množine. Po pôvodnom jobe 145681 obsahoval [manifest](ministral_3b_aya/training/data_manifest.json) **3 490 041 tréningových, 1 000 validačných a 1 000 testovacích príkladov**. Skript uchováva celé texty a limit 4 096 tokenov sa uplatňuje pri tokenizácii konkrétneho behu.
## Prostredie pre tréning ## 4. Kontrola, pilot a plný tréning
Tréning používa samostatné prostredie s LLaMA-Factory, Unslothom a CUDA pre H200. Balík obsahuje zachytené verzie knižníc. Funkčnosť konkrétnej zostavy sa overí pri prvom pilote na Perunovi. Konfigurácia [`configs/ministral.yaml`](configs/ministral.yaml) používa nastavenia plného behu s learning rate `0.0002`. Skript spustí štyri tréningové procesy cez DDP. Slurm preto dostane jeden task a štyri GPU na jednom uzle.
### Požiadavky a inštalácia prostredí
Priečinok `requirements` obsahuje verzie odvodené z exportu Hrušky.
| Súbor | Prostredie |
| --- | --- |
| `requirements/training.txt` | Python 3.11.16; LLaMA-Factory, Unsloth, PyTorch 2.6.0 s CUDA 12.4 a generatívna evaluácia. |
| `requirements/lm-eval.txt` | Python 3.11.16; lm-evaluation-harness, Transformers 4.48.2 a PyTorch 2.6.0 s CUDA 12.4. |
| `requirements/skbench.txt` | Python 3.13.17; export 308 balíkov SK-bench vrátane vLLM 0.28.0, Transformers 5.12.1 a PyTorch 2.13.0. |
| `requirements/skbench/pyproject.toml` a `uv.lock` | Pôvodné závislosti a uzamknuté verzie SK-bench pre obnovenie cez uv. |
Git závislosti LLaMA-Factory, lm-eval a lighteval sú pripnuté na pôvodné commity. Neplatná lokálna cesta k pip bola v tréningovom a lm-eval exporte nahradená verziou `pip==26.2.1` z Condy. Výpočtové knižnice zachovávajú pôvodné verzie.
Do tréningových požiadaviek boli doplnené `jieba==0.42.1`, `rouge-chinese==1.0.3`, `nltk==3.10.3` a jeho závislosti `joblib==1.6.0` a `defusedxml==0.7.1` pre generatívnu evaluáciu. Doplnený je aj `torchao==0.13.0`, pretože ho vyžaduje zachytená verzia Unsloth Zoo a export ho neobsahoval. Táto verzia TorchAO podporuje Python API s PyTorch 2.6; jej natívne C++ rozšírenia sú určené pre PyTorch 2.8. Scenár používa LoRA bez kvantizácie TorchAO.
Pre tréning je `setuptools` upravený na `80.10.2`, rovnako ako vo funkčnom SK-bench exporte. Knižnica jieba používa `pkg_resources`, ktorý bol zo setuptools 82 odstránený. Tréningové a benchmarkové verzie ostatných zachytených balíkov sa nemenia.
Ide o požiadavky pripravené z funkčných prostredí Hrušky a uvedených doplnení. Čistá inštalácia a výpočet na H200 sa overia na Perunovi.
#### Tréning a evaluácia na pripravených Aya dátach
Nasledujúce príkazy sa zadávajú z koreňa balíka v prostredí s dostupnou Condou a Gitom. PyTorch a xformers sa najprv nainštalujú z rovnakého oficiálneho CUDA 12.4 indexu:
```bash ```bash
conda create -n aya_train_perun python=3.11.16 pip=26.2.1 -y export RUN="$PROJECT/perun/runs/ministral_aya_$(date +%Y%m%d_%H%M%S)"
conda activate aya_train_perun "$PY" scripts/train.py configs/ministral.yaml --run-dir "$RUN" --dry-run
python -m pip install torch==2.6.0 torchvision==0.21.0 torchaudio==2.6.0 xformers==0.0.29.post3 \
--index-url https://download.pytorch.org/whl/cu124
python -m pip install -r requirements/training.txt
python -m pip check
``` ```
Na pridelenom GPU uzle sa overia importy, CUDA a BF16: Po úspešnej kontrole dát sa spúšťa pilot s 30 tréningovými krokmi a samostatným výstupným priečinkom. Rovnaká kontrola GPU a Tritonu sa používa aj pri plnom behu.
```bash
python -c 'import unsloth, torch, jieba, nltk; from rouge_chinese import Rouge; assert torch.cuda.is_available(); assert torch.cuda.is_bf16_supported(); print("GPU:", torch.cuda.get_device_name(0)); print("Viditelne GPU:", torch.cuda.device_count()); print("CUDA:", torch.version.cuda)'
```
Tréning aj `scripts/evaluate.py` sa potom spúšťajú v `aya_train_perun` podľa ďalších častí dokumentácie. Overené opravy generovania z Titanu sú súčasťou `scripts/train.py`.
#### lm-evaluation-harness
Príkazy sa zadávajú z koreňa balíka:
```bash
conda create -n aya_lmeval_perun python=3.11.16 pip=26.2.1 -y
conda activate aya_lmeval_perun
python -m pip install torch==2.6.0 --index-url https://download.pytorch.org/whl/cu124
python -m pip install -r requirements/lm-eval.txt
python -m pip check
lm_eval --help
```
Súbor inštaluje framework pri revízii `6d2abda4fd171e68a8789330c4149e37c1ca0bda`. Vlastné slovenské úlohy, zoznam benchmarkov a evaluačné dáta sa prenášajú samostatne; požiadavky obsahujú Python knižnice.
## 2. Pilot a tréning
Z koreňa balíka s aktivovaným tréningovým prostredím sa overí konfigurácia a integrita spoločných dát.
```bash
python scripts/train.py configs/ministral.yaml --dry-run
```
Plný tréning využíva štyri H200 na jednom uzle cez DDP. `train.py` spustí procesy sám. Slurm job preto požaduje jeden task a štyri GPU. Základná konfigurácia používa BF16, LoRA rank 16, alpha 32, batch 8 na GPU a efektívny batch 32. LR 0,0001 je východisková hodnota pre Ministral.
Prvý pilot overí načítanie modelu, jeho natívny formát chatu, priebeh lossu a uloženie adaptéra. `--pilot-steps 30` obmedzí počet krokov a pri predvolenom splite použije najviac 2 048 tréningových riadkov z pevného poradia. Celý validačný split zostane dostupný. Pilot použije vlastný výstupný priečinok. LLaMA-Factory načíta zdrojový JSONL a obmedzí rozsah pred tokenizáciou.
```bash ```bash
sbatch --job-name=ministral_pilot \ sbatch --job-name=ministral_pilot \
--account=perun26012578 --partition=gpu_short \ --account=perun26012578 --partition=gpu_short \
--nodes=1 --ntasks=1 --gres=gpu:4 --cpus-per-task=32 --mem=128G \ --nodes=1 --ntasks=1 --gres=gpu:4 --cpus-per-task=32 --mem=128G \
--time=02:00:00 --output=logs/ministral_pilot_%j.out \ --time=02:00:00 --output=logs/ministral_pilot_%j.out \
--wrap='python -u scripts/train.py configs/ministral.yaml --run-dir runs/ministral_pilot --pilot-steps 30' --wrap='set -e
"$PY" -c "import torch; assert torch.cuda.device_count() == 4; assert all(\"H200\" in torch.cuda.get_device_name(i) for i in range(4)); from triton.runtime import driver; print(driver.active.get_current_target())"
"$PY" -u scripts/train.py configs/ministral.yaml --run-dir "${RUN}_pilot" --pilot-steps 30'
``` ```
Plný tréning sa spustí po kontrole pilotu a spresnení času. Pri dlhom behu sa checkpointy ukladajú každých 1 000 optimizer krokov a vyberá sa podľa najnižšieho validačného lossu. Pri 300k pilote sa namiesto `--pilot-steps 30` použilo `--pilot-samples 300000 --pilot-steps 9375 --pilot-eval-steps 1875`. Plný beh sa odosiela po úspešnom pilote.
```bash ```bash
sbatch --job-name=ministral_train \ sbatch --job-name=ministral_full_lr020 \
--account=perun26012578 --partition=gpu_long \ --account=perun26012578 --partition=gpu_long \
--nodes=1 --ntasks=1 --gres=gpu:4 --cpus-per-task=32 --mem=256G \ --nodes=1 --ntasks=1 --gres=gpu:4 --cpus-per-task=32 --mem=256G \
--time=2-00:00:00 --output=logs/ministral_train_%j.out \ --time=2-00:00:00 --output=logs/ministral_full_lr020_%j.out \
--wrap='python -u scripts/train.py configs/ministral.yaml' --wrap='set -e
"$PY" -c "import torch; assert torch.cuda.device_count() == 4; assert all(\"H200\" in torch.cuda.get_device_name(i) for i in range(4)); from triton.runtime import driver; print(driver.active.get_current_target())"
"$PY" -u scripts/train.py configs/ministral.yaml --run-dir "$RUN"'
``` ```
Adaptér sa uloží do `runs/ministral/adapter`. V nadradenom priečinku zostanú `train.log`, `used_config.yaml` a `run.json` s identifikáciou datasetu. Pre iný beh sa zvolí nový `--run-dir`. Pri obnovení z checkpointu sa použije `--resume` s pôvodnými dátami a nastaveniami. V `$RUN` vzniknú `run.json`, `used_config.yaml`, `train.log` a `adapter/`. Pri pôvodnom jobe 148065 bol posledný checkpoint **109 064**. Export v samotnom `adapter/` zodpovedá validačne najlepšiemu checkpointu **109 000**.
## 3. Vyhodnotenie ## 5. Aya test posledného checkpointu
Po tréningu môže `evaluate.py` na jednej GPU vypočítať validačný loss a perplexitu a vygenerovať odpovede pre F1 a presnú zhodu. Pri generovaní sa ponechá priestor aspoň pre jeden nový token. Pri skórovaní generovaných odpovedí sa používajú úplné referencie zo spoločného JSONL. Výsledky obsahujú aj počty referencií, ktoré sa líšia od exportovaných labelov. Po výbere modelu sa môže použiť `--split test` s novým výstupným priečinkom. Nasledujúce cesty zodpovedajú testu pôvodného jobu 148065. Pri teste nového tréningu je potrebné nastaviť cestu k novému behu a k jeho poslednému checkpointu. Evaluácia vyžaduje pôvodný manifest dát daného behu.
```bash ```bash
python scripts/evaluate.py configs/ministral.yaml --output-dir results/ministral_eval export RUN="$PROJECT/perun/models/ministral-3b-instruct/runs/full_aya_lr_0p000200_epoch1"
python scripts/evaluate.py configs/ministral.yaml --split test --output-dir results/ministral_test export CHECKPOINT="$RUN/adapter/checkpoint-109064"
export EVAL_OUT="$RUN/evaluations/aya_test_last_109064_$(date +%Y%m%d_%H%M%S)"
sbatch --job-name=ministral_aya_test \
--account=perun26012578 --partition=gpu_short \
--nodes=1 --ntasks=1 --gres=gpu:1 --cpus-per-task=8 --mem=64G \
--time=04:00:00 --output=logs/ministral_aya_test_%j.out \
--wrap='set -e
"$PY" -u scripts/evaluate.py "$RUN/used_config.yaml" \
--adapter "$CHECKPOINT" --split test \
--output-dir "$EVAL_OUT" --max-new-tokens 256'
``` ```
Externý SK-bench sa spúšťa zo samostatného klonu v `/mnt/project/perun26012578/sk-bench`. Na porovnanie s baseline sa použije rovnaká revízia SK-bench, rovnaké nastavenia generovania a rovnaký súbor `tasks_ministral_sk_baseline.txt` s 340 variantmi. Lokálne exportovaný model musí byť viditeľný z kontajnera. [SK-bench na Slurme](https://github.com/slovak-nlp/sk-bench/blob/main/container/slurm/README.md) Súbor `summary.json` obsahuje testovací loss, perplexitu, tokenové F1 a normalizovanú presnú zhodu. Stav jobov sa sleduje cez `squeue -u "$USER"` a výpisy Slurmu sú v `logs/`.
Stav jobu sa sleduje cez `squeue -u "$USER"`. Pri chybách sa kontroluje log v `logs/`. Revízia Gitu sa v prípade potreby zistí pred odoslaním na prihlasovacom uzle.

165
perun/SK_BENCH.md Normal file
View File

@ -0,0 +1,165 @@
# SK-bench na Perune
Oba Ministraly sa hodnotia na rovnakom [zozname 385 slovenských zero-shot variantov](ministral_3b_aya/sk_bench/tasks_sk0_385.txt). Pôvodné výsledky vznikli v dvoch etapách po 340 a 45 variantoch. Nižšie je postup na prípravu prostredia, export modelu a vyhodnotenie celého dostupného zoznamu.
## 1. Príprava kontajnera
Cesty sa nastavia na `login01`. Ak sú klon, obraz a prostredie na Perune už pripravené, nasleduje príprava cache.
```bash
export PROJECT=/mnt/project/perun26012578
export APPHOME=/mnt/home/jascke919/apphome
export PY=/mnt/home/jascke919/aya_train_perun_managed/bin/python
export BENCH="$PROJECT/sk-bench"
export SKBENCH_REVISION=15823bbcc2d995e69441fcb7d34abb61bd8180c5
```
Pri prvej príprave je potrebné stiahnuť SK-bench v revízii pôvodných jobov. [SK-bench](https://github.com/slovak-nlp/sk-bench/tree/15823bbcc2d995e69441fcb7d34abb61bd8180c5)
```bash
git clone https://github.com/slovak-nlp/sk-bench.git "$BENCH"
git -C "$BENCH" checkout --detach "$SKBENCH_REVISION"
mkdir -p "$APPHOME"
```
Obraz Apptainera sa vytvorí na pridelenom CPU uzle. `container/build.sh` uloží `cuda.sif` do `APPHOME`.
```bash
srun --account=perun26012578 --partition=cpu_short \
--nodes=1 --ntasks=1 --cpus-per-task=4 --mem=24G --time=02:00:00 --pty bash
```
```bash
cd "$BENCH"
bash container/build.sh
bash container/shell.sh
```
V kontajneri je potrebné vytvoriť prostredie s Pythonom 3.13 a nainštalovať závislosti z `uv.lock`. V klone SK-bench sa používa jeho vlastný lockfile. Kópie v [`requirements/skbench/`](requirements/skbench/) majú zhodné kontrolné súčty s klonom na Perune.
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
export UV_PROJECT_ENVIRONMENT="$VIRTUAL_ENV"
uv venv --python 3.13 "$VIRTUAL_ENV"
uv sync --locked
uv run --locked python -m nltk.downloader punkt_tab wordnet omw-1.4
uv run --locked python --version
```
## 2. Cache základného modelu a datasetov
Zoznam úloh sa skopíruje do klonu SK-bench na hostiteľovi. Na CPU uzle s prístupom na internet sa z toho istého klonu spustí `container/shell.sh`.
```bash
cp "$PROJECT/perun/ministral_3b_aya/sk_bench/tasks_sk0_385.txt" "$BENCH/"
cd "$BENCH"
bash container/shell.sh
```
V kontajneri sa overí účet Hugging Face. Pri prvom prihlásení sa použije `uv run --locked hf auth login`. Pre Slohry, Chiby, Slovak Trivia a Word Analogies treba pod týmto účtom odsúhlasiť podmienky prístupu.
```bash
export UV_PROJECT_ENVIRONMENT="$VIRTUAL_ENV"
unset HF_DATASETS_OFFLINE HF_HUB_OFFLINE
uv run --locked hf auth whoami
uv run --locked python - <<'PYTHON'
from pathlib import Path
from huggingface_hub import snapshot_download
from lighteval.tasks.lighteval_task import LightevalTask
from src.tasks.task_registry import TASK_REGISTRY
snapshot_download(
"ministral/Ministral-3b-instruct",
revision="2c95908929198d6e69af8638f0dbbd9bc6b93f9e",
local_dir="models/ministral-base",
)
tasks = Path("tasks_sk0_385.txt").read_text(encoding="utf-8").splitlines()
assert len(tasks) == len(set(tasks)) == 385
for task in tasks:
name, shots = task.rsplit("|", 1)
config = TASK_REGISTRY.get(name)
if config is None or shots != "0":
raise ValueError(f"Neznámy zero-shot variant {task}")
print(f"Pripravujem {name}", flush=True)
LightevalTask(config).eval_docs()
PYTHON
```
GPU job používa cache z toho istého `APPHOME`. Po príprave sa príkazom `exit` opustí kontajner a podľa potreby aj CPU uzol.
## 3. Zlúčenie adaptéra pre SK-bench
Pôvodný benchmark bežal v klone `sk-bench`. Dotrénovaný model bol vyhodnotený zo samostatného klonu rovnakej revízie. Pre checkpoint z kroku 109 064 sa na `login01` nastavia nasledujúce cesty.
```bash
export RUN="$PROJECT/perun/models/ministral-3b-instruct/runs/full_aya_lr_0p000200_epoch1"
export CHECKPOINT="$RUN/adapter/checkpoint-109064"
export TUNED_SK="$PROJECT/perun/models/ministral-3b-instruct/benchmarks/sk-bench/finetuned_checkpoint_109064/sk-bench"
export BASE_MODEL="$BENCH/models/ministral-base"
export TUNED_MODEL="$TUNED_SK/models/ministral-last-109064"
```
Ak je zlúčený model už uložený, nasleduje evaluácia na GPU. Pri príprave od začiatku je potrebné vytvoriť druhý klon a použiť ten istý [exportný skript](scripts/export_skbench_model.py) ako pri jobe 152979. Skript overí váhy základného modelu proti pripnutej revízii v bežnej Hugging Face cache tréningového účtu. Túto revíziu pripraví krok v [tréningovom návode](README.md#2-príprava-prostredia).
```bash
mkdir -p "$(dirname "$TUNED_SK")"
git clone --local "$BENCH" "$TUNED_SK"
git -C "$TUNED_SK" checkout --detach "$SKBENCH_REVISION"
mkdir -p "$TUNED_SK/models" "$BENCH/logs"
sbatch --job-name=ministral_merge \
--account=perun26012578 --partition=cpu_short \
--nodes=1 --ntasks=1 --cpus-per-task=8 --mem=64G \
--time=02:00:00 --output="$BENCH/logs/ministral_merge_%j.out" \
--wrap='"$PY" -u "$PROJECT/perun/scripts/export_skbench_model.py" \
"$BASE_MODEL" "$CHECKPOINT" "$TUNED_MODEL"'
```
Úspešný job vypíše `MERGED_MODEL_READY` a uloží BF16 váhy so záznamom o použitom checkpointe.
## 4. Vyhodnotenie na GPU
Po príprave cache a zlúčení adaptéra sa z `login01` odošle jeden job na každý model. Pri oboch sa zachová rovnaký zoznam úloh, revízia a identifikátory modelov v JSON výstupoch. [Oficiálne spúšťanie SK-bench cez Slurm](https://github.com/slovak-nlp/sk-bench/blob/15823bbcc2d995e69441fcb7d34abb61bd8180c5/container/slurm/README.md)
```bash
export UV_PROJECT_ENVIRONMENT="$APPHOME/venv"
cp "$BENCH/tasks_sk0_385.txt" "$TUNED_SK/"
for entry in base:ministral-base tuned:ministral-last-109064; do
label=${entry%%:*}
model=${entry#*:}
if [[ $label == base ]]; then workdir=$BENCH; else workdir=$TUNED_SK; fi
mkdir -p "$workdir/logs"
sbatch --job-name="skbench385_$label" \
--account=perun26012578 --partition=gpu_short \
--nodes=1 --ntasks=1 --gres=gpu:1 --cpus-per-task=8 --mem=64G \
--time=04:00:00 --chdir="$workdir" \
--output="$workdir/logs/skbench385_${label}_%j.out" \
--wrap="srun --cpu-bind=none bash container/run-eval.sh models/$model \
--tensor-parallel-size 1 --tasks-file tasks_sk0_385.txt \
--output-dir outputs/skbench385_${label}_\$SLURM_JOB_ID"
done
```
## 5. Agregácia výsledkov
Po dokončení jobov sa zadajú ich ID podľa výpisu Slurmu. Pri agregácii pôvodnej dvojice etáp sa do `raw/` vložia všetky štyri súbory JSON. Oficiálny skript ich spojí podľa `config_general.model_name`.
```bash
cd "$BENCH"
read -r -p "ID jobu pôvodného modelu: " BASE_JOB
read -r -p "ID jobu dotrénovaného modelu: " TUNED_JOB
BASE_JSON=$(find "$BENCH/outputs/skbench385_base_$BASE_JOB" -type f -name 'results_*.json' -print -quit)
TUNED_JSON=$(find "$TUNED_SK/outputs/skbench385_tuned_$TUNED_JOB" -type f -name 'results_*.json' -print -quit)
REPORT=$(mktemp -d "$BENCH/outputs/ministral_leaderboard_XXXXXX")
mkdir "$REPORT/raw"
cp "$BASE_JSON" "$REPORT/raw/base.json"
cp "$TUNED_JSON" "$REPORT/raw/tuned.json"
"$PY" scripts/generate_final_results.py "$REPORT/raw" "$REPORT/aggregated"
"$PY" scripts/generate_leaderboard.py --output-dir "$REPORT" --leaderboard-dir "$REPORT/leaderboard"
```
Agregované súbory JSON sa uložia do `$REPORT/aggregated` a HTML prehľad do `$REPORT/leaderboard/index.html`. Porovnanie a Overall podľa metrík verejného SK-bench Space sú v [dokumentácii experimentu](ministral_3b_aya/README.md#čiastočné-výsledky-sk-bench).

View File

@ -29,7 +29,7 @@ lora_target: q_proj,k_proj,v_proj,o_proj,gate_proj,up_proj,down_proj
per_device_train_batch_size: 8 per_device_train_batch_size: 8
per_device_eval_batch_size: 8 per_device_eval_batch_size: 8
gradient_accumulation_steps: 1 gradient_accumulation_steps: 1
learning_rate: 0.0001 learning_rate: 0.0002
num_train_epochs: 1.0 num_train_epochs: 1.0
lr_scheduler_type: cosine lr_scheduler_type: cosine
warmup_ratio: 0.08 warmup_ratio: 0.08

View File

@ -1,59 +0,0 @@
# Jeden trening tohto modelu: 4 H200, DDP, Unsloth a bf16.
model_name_or_path: "slovak-nlp/mistral-sk-7b"
model_revision: "089497ae0d72018e9591895c54f774bf0b4a83a4"
stage: sft
do_train: true
do_eval: true
finetuning_type: lora
template: alpaca
use_unsloth: true
use_unsloth_gc: true
bf16: true
infer_dtype: bfloat16
flash_attn: sdpa
dataset_dir: data/aya_slovak_fixed
dataset: aya_train
eval_dataset: aya_eval
cutoff_len: 4096
packing: false
train_on_prompt: false
preprocessing_num_workers: 8
dataloader_num_workers: 2
overwrite_cache: false
lora_rank: 16
lora_alpha: 32
lora_dropout: 0.0
lora_target: q_proj,k_proj,v_proj,o_proj,gate_proj,up_proj,down_proj
per_device_train_batch_size: 8
per_device_eval_batch_size: 8
gradient_accumulation_steps: 1
learning_rate: 0.0001
num_train_epochs: 1.0
lr_scheduler_type: cosine
warmup_ratio: 0.08
optim: adamw_torch
weight_decay: 0.0
max_grad_norm: 0.5
gradient_checkpointing: true
ddp_find_unused_parameters: false
output_dir: runs/mistral/adapter
overwrite_output_dir: false
logging_steps: 10
eval_strategy: steps
eval_steps: 1000
save_strategy: steps
save_steps: 1000
save_total_limit: 2
save_only_model: false
load_best_model_at_end: true
metric_for_best_model: loss
greater_is_better: false
predict_with_generate: false
plot_loss: true
report_to: none
seed: 42
data_seed: 42

View File

@ -1,59 +0,0 @@
# Jeden trening tohto modelu: 4 H200, DDP, Unsloth a bf16.
model_name_or_path: "allenai/OLMo-2-1124-7B"
model_revision: "7df9a82518afdecae4e8c026b27adccc8c1f0032"
stage: sft
do_train: true
do_eval: true
finetuning_type: lora
template: alpaca
use_unsloth: true
use_unsloth_gc: true
bf16: true
infer_dtype: bfloat16
flash_attn: sdpa
dataset_dir: data/aya_slovak_fixed
dataset: aya_train
eval_dataset: aya_eval
cutoff_len: 4096
packing: false
train_on_prompt: false
preprocessing_num_workers: 8
dataloader_num_workers: 2
overwrite_cache: false
lora_rank: 16
lora_alpha: 32
lora_dropout: 0.0
lora_target: q_proj,k_proj,v_proj,o_proj,gate_proj,up_proj,down_proj
per_device_train_batch_size: 8
per_device_eval_batch_size: 8
gradient_accumulation_steps: 1
learning_rate: 0.0001
num_train_epochs: 1.0
lr_scheduler_type: cosine
warmup_ratio: 0.08
optim: adamw_torch
weight_decay: 0.0
max_grad_norm: 0.5
gradient_checkpointing: true
ddp_find_unused_parameters: false
output_dir: runs/olmo/adapter
overwrite_output_dir: false
logging_steps: 10
eval_strategy: steps
eval_steps: 1000
save_strategy: steps
save_steps: 1000
save_total_limit: 2
save_only_model: false
load_best_model_at_end: true
metric_for_best_model: loss
greater_is_better: false
predict_with_generate: false
plot_loss: true
report_to: none
seed: 42
data_seed: 42

File diff suppressed because one or more lines are too long

View File

@ -1,131 +0,0 @@
# Python 3.11.16; CUDA 12.4.
absl-py==2.5.0
accelerate==1.15.0
aiohappyeyeballs==2.7.1
aiohttp==3.14.3
aiosignal==1.4.0
annotated-doc==0.0.5
annotated-types==0.8.0
anyio==4.15.1
attrs==26.1.0
certifi==2026.7.22
chardet==6.0.0.post1
charset-normalizer==3.5.2
click==8.5.0
cloudpickle==3.1.2
colorama==0.4.6
contourpy==1.3.3
cycler==0.12.1
DataProperty==1.1.1
datasets==5.0.1
defusedxml==0.7.1
dill==0.4.1
evaluate==0.4.6
filelock==3.32.3
fonttools==4.66.1
frozenlist==1.8.0
fsspec==2026.6.0
googleapis-common-protos==1.75.5
h11==0.16.0
hf-xet==1.6.0
httpcore==1.0.9
httpx==0.28.1
huggingface_hub==0.36.2
idna==3.20
Jinja2==3.1.6
joblib==1.6.0
jsonlines==4.0.0
kiwisolver==1.5.1
lm_eval @ git+https://github.com/EleutherAI/lm-evaluation-harness.git@6d2abda4fd171e68a8789330c4149e37c1ca0bda
lxml==6.1.3
markdown-it-py==4.2.0
MarkupSafe==3.0.3
matplotlib==3.11.2
mbstrdecoder==1.1.5
mdurl==0.1.2
more-itertools==11.1.0
mpmath==1.3.0
multidict==6.9.1
multiprocess==0.70.19
narwhals==2.26.0
networkx==3.6.1
nltk==3.10.3
numexpr==2.14.2
numpy==2.4.6
nvidia-cublas-cu12==12.4.5.8
nvidia-cuda-cupti-cu12==12.4.127
nvidia-cuda-nvrtc-cu12==12.4.127
nvidia-cuda-runtime-cu12==12.4.127
nvidia-cudnn-cu12==9.1.0.70
nvidia-cufft-cu12==11.2.1.3
nvidia-curand-cu12==10.3.5.147
nvidia-cusolver-cu12==11.6.1.9
nvidia-cusparse-cu12==12.3.1.170
nvidia-cusparselt-cu12==0.6.2
nvidia-nccl-cu12==2.21.5
nvidia-nvjitlink-cu12==12.4.127
nvidia-nvtx-cu12==12.4.127
opentelemetry-api==1.45.0
opentelemetry-exporter-http-transport==0.66b0
opentelemetry-exporter-otlp-common==0.66b0
opentelemetry-exporter-otlp-proto-common==1.45.0
opentelemetry-exporter-otlp-proto-http==1.45.0
opentelemetry-proto==1.45.0
opentelemetry-sdk==1.45.0
opentelemetry-semantic-conventions==0.66b0
packaging==26.3
pandas==3.0.6
pathvalidate==3.3.1
peft==0.14.0
pillow==12.3.0
pip==26.2.1
platformdirs==4.12.2
portalocker==4.4.0
propcache==0.5.4
protobuf==7.36.2
psutil==7.2.2
pyarrow==25.0.1
pybind11==3.1.0
pydantic==2.13.5
pydantic_core==2.46.5
Pygments==2.21.0
pyparsing==3.3.3
pytablewriter==1.2.1
python-dateutil==2.9.0.post0
pytz==2026.4
PyYAML==6.0.3
regex==2026.9.29
requests==2.34.2
rich==15.0.0
rouge_score==0.1.2
sacrebleu==2.6.0
safetensors==0.8.0
scikit-learn==1.9.1
scipy==1.17.1
sentencepiece==0.2.2
setuptools==84.0.0
shellingham==1.5.4
six==1.17.0
sqlitedict==2.1.0
sympy==1.13.1
tabledata==1.3.5
tabulate==0.10.0
tcolorpy==0.1.7
threadpoolctl==3.7.0
tokenizers==0.21.4
torch==2.6.0+cu124
tqdm==4.70.1
tqdm-multiprocess==0.0.11
transformers==4.48.2
triton==3.2.0
typepy==1.3.5
typer==0.27.2
typing-inspection==0.4.4
typing_extensions==4.16.0
urllib3==2.8.0
wandb==0.30.0
wheel==0.48.0
word2number==1.1
xxhash==4.0.1
yarl==1.25.1
zstandard==0.25.0

View File

@ -1,309 +0,0 @@
# Python 3.13.17;
absl-py==2.4.0
accelerate==1.14.0
aenum==3.1.15
aioboto3==15.5.0
aiobotocore==2.25.1
aiofiles==25.1.0
aiohappyeyeballs==2.7.1
aiohttp==3.14.1
aioitertools==0.13.0
aiosignal==1.4.0
annotated-doc==0.0.4
annotated-types==0.7.0
anthropic==0.116.0
antlr4-python3-runtime==4.13.2
anyio==4.14.1
apache-tvm-ffi==0.1.11
astor==0.8.1
attrs==26.1.0
beautifulsoup4==4.15.0
blake3==1.0.9
blis==1.3.3
boto3==1.40.61
botocore==1.40.61
cachetools==7.1.4
catalogue==2.0.10
cbor2==6.1.2
certifi==2026.6.17
cffi==2.0.0
cfgv==3.5.0
chardet==6.0.0.post1
charset-normalizer==3.4.7
click==8.4.2
cloudpathlib==0.24.0
cloudpickle==3.1.2
colorama==0.4.6
colorlog==6.10.1
compressed-tensors==0.17.0
confection==1.3.3
cryptography==49.0.0
cuda-bindings==13.3.1
cuda-core==1.0.1
cuda-pathfinder==1.5.6
cuda-python==13.3.1
cuda-tile==1.5.0
cuda-toolkit==13.0.3.0
cymem==2.0.13
dataproperty==1.1.1
datasets==5.0.0
debugpy==1.8.21
depyf==0.20.0
detect-installer==0.1.0
dill==0.4.1
diskcache==5.6.3
distlib==0.4.3
distro==1.9.0
dnspython==2.8.0
docstring-parser==0.18.0
einops==0.8.2
email-validator==2.3.0
emoji==2.15.0
fastapi==0.136.3
fastapi-cli==0.0.27
fastapi-cloud-cli==0.22.1
fastar==0.11.0
fastsafetensors==0.3.3
fastuuid==0.14.0
filelock==3.29.5
flashinfer-python==0.6.16.post3
frozendict==2.4.7
frozenlist==1.8.0
fsspec==2025.9.0
gitdb==4.0.12
gitpython==3.1.50
googleapis-common-protos==1.75.0
grpcio==1.81.1
h11==0.16.0
hf==1.29.0
hf-xet==1.6.0
httpcore==1.0.9
httptools==0.8.0
httpx==0.28.1
httpx-sse==0.4.3
huggingface-hub==1.29.0
humming-kernels==0.1.12
identify==2.6.19
idna==3.18
ijson==3.5.0
immutabledict==4.3.1
importlib-metadata==8.9.0
inspect-ai==0.3.145
interegular==0.3.3
jieba==0.42.1
jinja2==3.1.6
jiter==0.16.0
jmespath==1.1.0
joblib==1.5.3
jsonlines==4.0.0
jsonpatch==1.33
jsonpath-ng==1.8.0
jsonpointer==3.1.1
jsonref==1.1.0
jsonschema==4.26.0
jsonschema-specifications==2025.9.1
langcodes==3.5.1
langdetect==1.0.9
lark==1.2.2
latex2sympy2-extended==1.0.6
lighteval @ git+https://github.com/huggingface/lighteval.git@10b9104eb16778b174e9e240140974a039018981
linkify-it-py==2.1.0
litellm==1.90.2
llguidance==1.7.6
llvmlite==0.47.0
lm-format-enforcer==0.11.3
loguru==0.7.3
lxml==6.1.1
markdown-it-py==4.2.0
markupsafe==3.0.3
mbstrdecoder==1.1.5
mcp==1.28.1
mdit-py-plugins==0.6.1
mdurl==0.1.2
mistral-common==1.11.7
ml-dtypes==0.5.4
mmh3==5.2.1
model-hosting-container-standards==0.1.16
more-itertools==11.1.0
mpmath==1.3.0
msgpack==1.2.1
msgspec==0.21.1
multidict==6.7.1
multiprocess==0.70.19
murmurhash==1.0.15
narwhals==2.23.0
natto-py==1.0.1
nccl4py==0.4.1
nest-asyncio==1.6.0
networkx==3.6.1
ninja==1.13.0
nltk==3.9.1
nodeenv==1.10.0
numba==0.65.0
numpy==2.4.6
nvidia-cublas==13.1.1.3
nvidia-cuda-cccl==13.3.3.4.1
nvidia-cuda-crt==13.3.73
nvidia-cuda-cupti==13.0.85
nvidia-cuda-nvcc==13.2.78
nvidia-cuda-nvdisasm==13.3.73
nvidia-cuda-nvrtc==13.0.88
nvidia-cuda-runtime==13.0.96
nvidia-cudnn-cu13==9.20.0.48
nvidia-cudnn-frontend==1.25.0
nvidia-cufft==12.0.0.61
nvidia-cufile==1.15.1.6
nvidia-curand==10.4.0.35
nvidia-cusolver==12.0.4.66
nvidia-cusparse==12.6.3.3
nvidia-cusparselt-cu13==0.8.1
nvidia-cutlass-dsl==4.6.2
nvidia-cutlass-dsl-libs-base==4.6.2
nvidia-cutlass-dsl-libs-core==4.6.2
nvidia-cutlass-dsl-libs-cu12==4.6.2
nvidia-cutlass-dsl-libs-cu13==4.6.2
nvidia-ml-py==13.610.43
nvidia-nccl-cu13==2.29.7
nvidia-nvjitlink==13.3.33
nvidia-nvshmem-cu13==3.4.5
nvidia-nvtx==13.0.85
nvidia-nvvm==13.2.78
nvtx==0.2.15
openai==2.44.0
openai-harmony==0.0.8
opencv-python-headless==5.0.0.93
opentelemetry-api==1.43.0
opentelemetry-exporter-otlp==1.43.0
opentelemetry-exporter-otlp-proto-common==1.43.0
opentelemetry-exporter-otlp-proto-grpc==1.43.0
opentelemetry-exporter-otlp-proto-http==1.43.0
opentelemetry-proto==1.43.0
opentelemetry-sdk==1.43.0
opentelemetry-semantic-conventions==0.64b0
opentelemetry-semantic-conventions-ai==0.5.1
outlines-core==0.2.14
packaging==26.2
pandas==3.0.3
partial-json-parser==0.2.1.1.post7
pathlib-abc==0.5.2
pathvalidate==3.3.1
pillow==12.3.0
pip==26.1.2
platformdirs==4.10.0
portalocker==3.2.0
pre-commit==4.6.0
preshed==3.0.13
prometheus-client==0.25.0
prometheus-fastapi-instrumentator==8.0.2
propcache==0.5.2
protobuf==6.33.6
psutil==7.2.2
py-cpuinfo==9.0.0
pyarrow==24.0.0
pybase64==1.4.3
pycountry==26.2.16
pycparser==3.0
pydantic==2.13.4
pydantic-core==2.46.4
pydantic-extra-types==2.11.1
pydantic-settings==2.14.2
pygments==2.20.0
pyjwt==2.13.0
pynvvideocodec==2.0.4
pytablewriter==1.2.1
pythainlp==5.3.4
python-crfsuite==0.9.12
python-dateutil==2.9.0.post0
python-discovery==1.4.2
python-dotenv==1.2.2
python-json-logger==4.1.0
python-multipart==0.0.32
pytz==2026.2
pyvi==0.1.1
pyyaml==6.0.3
pyzmq==27.1.0
quack-kernels==0.6.4
ray==2.56.0
referencing==0.37.0
regex==2026.6.28
requests==2.34.2
rich==15.0.0
rich-toolkit==0.20.1
rignore==0.7.6
rouge-score==0.1.2
rpds-py==2026.6.3
s3fs==2025.9.0
s3transfer==0.14.0
sacrebleu==2.6.0
safetensors==0.8.0
scikit-learn==1.9.0
scipy==1.18.0
semver==3.0.4
sentencepiece==0.2.1
sentry-sdk==2.64.0
setproctitle==1.3.7
setuptools==80.10.2
shellingham==1.5.4
shortuuid==1.0.13
six==1.17.0
sklearn-crfsuite==0.5.0
smart-open==8.0.0
smmap==5.0.3
sniffio==1.3.1
soupsieve==2.8.4
spacy==3.8.14
spacy-legacy==3.0.12
spacy-loggers==1.0.5
srsly==2.5.3
sse-starlette==3.4.5
stanza==1.13.0
starlette==1.3.1
sudachidict-core==20260428
sudachipy==0.6.11
supervisor==4.3.0
sympy==1.14.0
tabledata==1.3.5
tabulate==0.10.0
tcolorpy==0.1.7
tenacity==9.1.4
termcolor==2.3.0
textual==8.2.8
thinc==8.3.13
threadpoolctl==3.6.0
tiktoken==0.13.0
tilelang==0.1.12
tokenizers==0.22.2
tokenspeed-mla==0.1.8
tokenspeed-triton==3.7.10.post20260531
torch==2.13.0
torch-c-dlpack-ext==0.1.5
torchaudio==2.11.0
torchcodec==0.16.0
torchvision==0.28.0
tqdm==4.68.3
transformers==5.12.1
triton==3.7.1
typepy==1.3.5
typer==0.26.8
typing-extensions==4.16.0
typing-inspection==0.4.2
uc-micro-py==2.0.0
udapi==0.5.2
udtools==0.2.8
universal-pathlib==0.3.10
urllib3==2.7.0
uvicorn==0.49.0
uvloop==0.22.1
virtualenv==21.5.1
vllm==0.28.0
wandb==0.28.0
wasabi==1.1.3
watchfiles==1.2.0
weasel==1.0.0
websockets==16.0
wrapt==1.17.3
xgrammar==0.2.3
xxhash==3.8.0
yarl==1.24.2
z3-solver==4.15.4.0
zipp==4.1.0

View File

@ -0,0 +1,61 @@
import hashlib
import json
import os
from pathlib import Path
import shutil
import sys
import torch
from peft import PeftModel
from safetensors import safe_open
from transformers import AutoModelForCausalLM
base, checkpoint, target = (Path(x) for x in sys.argv[1:])
base_revision = '2c95908929198d6e69af8638f0dbbd9bc6b93f9e'
pinned = Path.home()/'.cache/huggingface/hub/models--ministral--Ministral-3b-instruct/snapshots'/base_revision
stage = target.with_name(target.name + '_staging_' + os.environ['SLURM_JOB_ID'])
def digest(path):
with path.open('rb') as handle:
return hashlib.file_digest(handle, 'sha256').hexdigest()
if target.exists():
raise SystemExit('Merged output already exists')
index = 'model.safetensors.index.json'
metadata = ('config.json','generation_config.json','special_tokens_map.json','tokenizer_config.json','tokenizer.json')
original_keys = json.loads((base/index).read_text())['weight_map']
for name in (*metadata, index, *sorted(set(original_keys.values()))):
if digest(base/name) != digest(pinned/name):
raise RuntimeError(f'Baseline file differs from the pinned training snapshot: {name}')
print('Baseline model matches the pinned training snapshot', base_revision, flush=True)
torch.set_num_threads(8)
model = AutoModelForCausalLM.from_pretrained(str(base), dtype=torch.float32, local_files_only=True, trust_remote_code=False)
lora = PeftModel.from_pretrained(model, str(checkpoint), is_trainable=False)
with torch.no_grad():
merged = lora.merge_and_unload(safe_merge=True).to(torch.bfloat16)
stage.mkdir()
merged.save_pretrained(str(stage), safe_serialization=True, max_shard_size='3GB')
for name in metadata:
shutil.copy2(base/name, stage/name)
saved_keys = json.loads((stage/index).read_text())['weight_map']
if set(saved_keys) != set(original_keys) or any('lora_' in key for key in saved_keys):
raise RuntimeError('Unexpected merged parameter set')
for key, expected_to_change in (
('model.layers.0.self_attn.q_proj.weight', True),
('model.embed_tokens.weight', False),
):
with safe_open(str(base/original_keys[key]), framework='pt', device='cpu') as left:
with safe_open(str(stage/saved_keys[key]), framework='pt', device='cpu') as right:
original, merged_tensor = left.get_tensor(key), right.get_tensor(key)
changed = not torch.equal(original, merged_tensor)
if merged_tensor.dtype != torch.bfloat16 or changed != expected_to_change:
raise RuntimeError(f'Unexpected merged tensor: {key}')
(stage/'merge_provenance.json').write_text(json.dumps({
'checkpoint': str(checkpoint), 'adapter_sha256': digest(checkpoint/'adapter_model.safetensors'),
'base_revision': base_revision, 'output_dtype': 'bfloat16',
'tokenizer_and_config': {name: digest(stage/name) for name in metadata},
}, indent=2) + chr(10))
stage.rename(target)
print('MERGED_MODEL_READY', target, flush=True)

View File

@ -251,6 +251,8 @@ def main() -> int:
parser.add_argument("--pilot-steps", type=int, help="Limit a pilot to this many optimizer steps; requires --run-dir.") parser.add_argument("--pilot-steps", type=int, help="Limit a pilot to this many optimizer steps; requires --run-dir.")
parser.add_argument("--pilot-samples", type=int, parser.add_argument("--pilot-samples", type=int,
help="Maximum rows per dataset in a pilot (default: 2048 or all eval rows, whichever is larger).") help="Maximum rows per dataset in a pilot (default: 2048 or all eval rows, whichever is larger).")
parser.add_argument("--pilot-eval-steps", type=int,
help="Evaluate and save at this interval in a pilot (default: at the last step).")
parser.add_argument("--dry-run", action="store_true", help="Validate data/config; do not import GPU libraries.") parser.add_argument("--dry-run", action="store_true", help="Validate data/config; do not import GPU libraries.")
parser.add_argument("--worker", action="store_true", help=argparse.SUPPRESS) parser.add_argument("--worker", action="store_true", help=argparse.SUPPRESS)
args = parser.parse_args() args = parser.parse_args()
@ -263,6 +265,8 @@ def main() -> int:
parser.error("--pilot-steps must be positive and requires --run-dir.") parser.error("--pilot-steps must be positive and requires --run-dir.")
if args.pilot_samples is not None and args.pilot_samples < 1: if args.pilot_samples is not None and args.pilot_samples < 1:
parser.error("--pilot-samples must be positive.") parser.error("--pilot-samples must be positive.")
if args.pilot_eval_steps is not None and not 1 <= args.pilot_eval_steps <= (args.pilot_steps or 0):
parser.error("--pilot-eval-steps requires --pilot-steps and must be between 1 and that limit.")
config = read_config(args.config.resolve()) config = read_config(args.config.resolve())
if config.get("stage") != "sft" or config.get("finetuning_type") != "lora": if config.get("stage") != "sft" or config.get("finetuning_type") != "lora":
raise ValueError("Expected an SFT / LoRA training configuration.") raise ValueError("Expected an SFT / LoRA training configuration.")
@ -283,9 +287,10 @@ def main() -> int:
sample_limit = args.pilot_samples or max(2048, eval_rows) sample_limit = args.pilot_samples or max(2048, eval_rows)
if sample_limit < eval_rows: if sample_limit < eval_rows:
parser.error(f"--pilot-samples must be at least {eval_rows} to retain the full eval split.") parser.error(f"--pilot-samples must be at least {eval_rows} to retain the full eval split.")
eval_interval = args.pilot_eval_steps or args.pilot_steps
config.update(max_steps=args.pilot_steps, max_samples=sample_limit, config.update(max_steps=args.pilot_steps, max_samples=sample_limit,
eval_strategy="steps", save_strategy="steps", eval_strategy="steps", save_strategy="steps",
eval_steps=args.pilot_steps, save_steps=args.pilot_steps) eval_steps=eval_interval, save_steps=eval_interval)
if args.run_dir: if args.run_dir:
config["output_dir"] = str(args.run_dir.resolve() / "adapter") config["output_dir"] = str(args.run_dir.resolve() / "adapter")
run_dir = Path(config["output_dir"]).parent run_dir = Path(config["output_dir"]).parent
@ -305,7 +310,8 @@ def main() -> int:
"train_rows": train_rows, "eval_rows": eval_rows, "train_rows": train_rows, "eval_rows": eval_rows,
"adapter_output": config["output_dir"]} "adapter_output": config["output_dir"]}
if args.pilot_steps is not None: if args.pilot_steps is not None:
summary.update(pilot_steps=args.pilot_steps, pilot_train_rows=min(train_rows, config["max_samples"])) summary.update(pilot_steps=args.pilot_steps, pilot_train_rows=min(train_rows, config["max_samples"]),
pilot_eval_steps=config["eval_steps"])
print(json.dumps(summary, ensure_ascii=False, indent=2)) print(json.dumps(summary, ensure_ascii=False, indent=2))
if args.dry_run: if args.dry_run:
print("Configuration/data checks passed. No training started.") print("Configuration/data checks passed. No training started.")