Spaces:
Sleeping
Sleeping
Commit ·
4f973e3
1
Parent(s): c887602
added evaluation metrics & evaluated small model
Browse files- README.md +18 -0
- environment.yml +5 -0
- notebooks/train.ipynb +153 -11
- pyproject.toml +8 -0
- requirements.txt +6 -0
README.md
CHANGED
|
@@ -9,6 +9,24 @@ A compact, encoder–decoder Transformer packaged so it can be used both as a Py
|
|
| 9 |
- CLI convenience commands for inference, serving, UI launch, and Hugging Face model downloads.
|
| 10 |
- Makefile shortcuts, notebooks, and Docker dev environment for day-to-day work.
|
| 11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
## Hugging Face Spaces DEMO
|
| 13 |
Here you can find a Huggingface Spaces for a quick demo
|
| 14 |
https://huggingface.co/spaces/AlaBoussoffara/Mini-Transformer
|
|
|
|
| 9 |
- CLI convenience commands for inference, serving, UI launch, and Hugging Face model downloads.
|
| 10 |
- Makefile shortcuts, notebooks, and Docker dev environment for day-to-day work.
|
| 11 |
|
| 12 |
+
## Models
|
| 13 |
+
|
| 14 |
+
### Small Model
|
| 15 |
+
The small model is a compact encoder-decoder transformer designed for efficient machine translation. Architecture details:
|
| 16 |
+
- Parameters: ~25M
|
| 17 |
+
- Architecture: 4 encoder/decoder layers, 4 attention heads, d_model=512, d_ff=1024
|
| 18 |
+
- Vocabulary: BPE tokenizer with 8K tokens
|
| 19 |
+
- Maximum sequence length: 128 tokens
|
| 20 |
+
|
| 21 |
+
Performance metrics (tested on 10K samples):
|
| 22 |
+
- BLEU score: 25.13 (EN-FR translation)
|
| 23 |
+
- Token accuracy: 63.43%
|
| 24 |
+
- Perplexity: 3.532
|
| 25 |
+
- Latency: 38.5ms average, 48.1ms p95 per sentence
|
| 26 |
+
- Additional metrics: chrF: 56.17, ROUGE-1: 59.19%, ROUGE-2: 40.03%
|
| 27 |
+
|
| 28 |
+
The model demonstrates good balance between performance and computational efficiency, making it suitable for deployment in resource-constrained environments while maintaining reasonable translation quality.
|
| 29 |
+
|
| 30 |
## Hugging Face Spaces DEMO
|
| 31 |
Here you can find a Huggingface Spaces for a quick demo
|
| 32 |
https://huggingface.co/spaces/AlaBoussoffara/Mini-Transformer
|
environment.yml
CHANGED
|
@@ -26,3 +26,8 @@ dependencies:
|
|
| 26 |
- mypy
|
| 27 |
- pre-commit
|
| 28 |
- httpx
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
- mypy
|
| 27 |
- pre-commit
|
| 28 |
- httpx
|
| 29 |
+
- evaluate
|
| 30 |
+
- sacrebleu
|
| 31 |
+
- rouge-score
|
| 32 |
+
- bert-score
|
| 33 |
+
- tqdm
|
notebooks/train.ipynb
CHANGED
|
@@ -103,12 +103,14 @@
|
|
| 103 |
"cfg = compose(\n",
|
| 104 |
" config_name=\"train_mode\",\n",
|
| 105 |
" overrides=[\n",
|
| 106 |
-
" \"model=
|
| 107 |
-
" \"tokenizer=
|
| 108 |
" \"dataset=medium_dataset\",\n",
|
| 109 |
" \"trainer.batch_size=32\",\n",
|
| 110 |
" \"trainer.gradient_accumulation=16\",\n",
|
| 111 |
" \"trainer.epochs=10\",\n",
|
|
|
|
|
|
|
| 112 |
" ],\n",
|
| 113 |
")\n",
|
| 114 |
"scfg_temp = OmegaConf.merge(OmegaConf.structured(TrainAppCfg), cfg)\n",
|
|
@@ -221,7 +223,7 @@
|
|
| 221 |
"name": "stdout",
|
| 222 |
"output_type": "stream",
|
| 223 |
"text": [
|
| 224 |
-
"Total number of parameters:
|
| 225 |
]
|
| 226 |
}
|
| 227 |
],
|
|
@@ -444,7 +446,7 @@
|
|
| 444 |
"name": "stdout",
|
| 445 |
"output_type": "stream",
|
| 446 |
"text": [
|
| 447 |
-
"
|
| 448 |
]
|
| 449 |
}
|
| 450 |
],
|
|
@@ -757,7 +759,7 @@
|
|
| 757 |
},
|
| 758 |
{
|
| 759 |
"cell_type": "code",
|
| 760 |
-
"execution_count":
|
| 761 |
"id": "439d4094",
|
| 762 |
"metadata": {},
|
| 763 |
"outputs": [
|
|
@@ -765,7 +767,22 @@
|
|
| 765 |
"name": "stderr",
|
| 766 |
"output_type": "stream",
|
| 767 |
"text": [
|
| 768 |
-
"Epoch
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 769 |
]
|
| 770 |
}
|
| 771 |
],
|
|
@@ -1041,13 +1058,60 @@
|
|
| 1041 |
"execution_count": null,
|
| 1042 |
"id": "39f61da8",
|
| 1043 |
"metadata": {},
|
| 1044 |
-
"outputs": [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1045 |
"source": [
|
| 1046 |
-
"# test the model\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1047 |
"model.eval()\n",
|
| 1048 |
"test_loss = 0.0\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1049 |
"with torch.no_grad(), autocast_ctx:\n",
|
| 1050 |
" for src, tgt in tqdm(test_dataloader, desc=\"Testing\"):\n",
|
|
|
|
| 1051 |
" encoded_src = tokenizer(\n",
|
| 1052 |
" src,\n",
|
| 1053 |
" padding=True,\n",
|
|
@@ -1067,18 +1131,96 @@
|
|
| 1067 |
" tgt_ids = encoded_tgt[\"input_ids\"].to(device)\n",
|
| 1068 |
" decoder_in = tgt_ids[:, :-1]\n",
|
| 1069 |
" labels = tgt_ids[:, 1:]\n",
|
| 1070 |
-
" tgt_padd_mask = decoder_in.eq(
|
| 1071 |
"\n",
|
|
|
|
| 1072 |
" logits = model(src_ids, decoder_in, src_padd_mask, tgt_padd_mask)\n",
|
| 1073 |
" loss = criterion(logits.reshape(-1, logits.size(-1)), labels.reshape(-1))\n",
|
| 1074 |
" test_loss += loss.item()\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1075 |
"avg_test_loss = test_loss / max(1, len(test_dataloader))\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1076 |
"print(f\"Test Loss: {avg_test_loss:.4f}\")\n",
|
| 1077 |
-
"
|
| 1078 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1079 |
" \"test_loss\": avg_test_loss,\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1080 |
" \"timestamp\": datetime.now().isoformat(),\n",
|
|
|
|
|
|
|
|
|
|
| 1081 |
"}\n",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1082 |
"manifest_path.write_text(json.dumps(manifest_data, indent=2))"
|
| 1083 |
]
|
| 1084 |
},
|
|
|
|
| 103 |
"cfg = compose(\n",
|
| 104 |
" config_name=\"train_mode\",\n",
|
| 105 |
" overrides=[\n",
|
| 106 |
+
" \"model=small_model\",\n",
|
| 107 |
+
" \"tokenizer=bpe_8k\",\n",
|
| 108 |
" \"dataset=medium_dataset\",\n",
|
| 109 |
" \"trainer.batch_size=32\",\n",
|
| 110 |
" \"trainer.gradient_accumulation=16\",\n",
|
| 111 |
" \"trainer.epochs=10\",\n",
|
| 112 |
+
" \"trainer.resume=best\",\n",
|
| 113 |
+
" \"model.best_checkpoint_path=trained_models/small_model_v1/checkpoints/best/checkpoint.pt\",\n",
|
| 114 |
" ],\n",
|
| 115 |
")\n",
|
| 116 |
"scfg_temp = OmegaConf.merge(OmegaConf.structured(TrainAppCfg), cfg)\n",
|
|
|
|
| 223 |
"name": "stdout",
|
| 224 |
"output_type": "stream",
|
| 225 |
"text": [
|
| 226 |
+
"Total number of parameters: 25204736\n"
|
| 227 |
]
|
| 228 |
}
|
| 229 |
],
|
|
|
|
| 446 |
"name": "stdout",
|
| 447 |
"output_type": "stream",
|
| 448 |
"text": [
|
| 449 |
+
"Loading checkpoint from /mnt/shared/Local/Projets/Mini-Transformer/trained_models/small_model_v1/checkpoints/best/checkpoint.pt\n"
|
| 450 |
]
|
| 451 |
}
|
| 452 |
],
|
|
|
|
| 759 |
},
|
| 760 |
{
|
| 761 |
"cell_type": "code",
|
| 762 |
+
"execution_count": 13,
|
| 763 |
"id": "439d4094",
|
| 764 |
"metadata": {},
|
| 765 |
"outputs": [
|
|
|
|
| 767 |
"name": "stderr",
|
| 768 |
"output_type": "stream",
|
| 769 |
"text": [
|
| 770 |
+
"Epoch 10 [train]: 0%| | 33/57063 [00:03<1:47:43, 8.82it/s]\n",
|
| 771 |
+
"Epoch 10 [train]: 0%| | 33/57063 [00:03<1:47:43, 8.82it/s]\n"
|
| 772 |
+
]
|
| 773 |
+
},
|
| 774 |
+
{
|
| 775 |
+
"ename": "KeyboardInterrupt",
|
| 776 |
+
"evalue": "",
|
| 777 |
+
"output_type": "error",
|
| 778 |
+
"traceback": [
|
| 779 |
+
"\u001b[31m---------------------------------------------------------------------------\u001b[39m",
|
| 780 |
+
"\u001b[31mKeyboardInterrupt\u001b[39m Traceback (most recent call last)",
|
| 781 |
+
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[13]\u001b[39m\u001b[32m, line 59\u001b[39m\n\u001b[32m 57\u001b[39m loss = loss / grad_accum\n\u001b[32m 58\u001b[39m scaled_loss = scaler.scale(loss)\n\u001b[32m---> \u001b[39m\u001b[32m59\u001b[39m \u001b[43mscaled_loss\u001b[49m\u001b[43m.\u001b[49m\u001b[43mbackward\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 60\u001b[39m accum_counter += \u001b[32m1\u001b[39m\n\u001b[32m 62\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m accum_counter == grad_accum:\n",
|
| 782 |
+
"\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/envs/mini-transformer/lib/python3.11/site-packages/torch/_tensor.py:525\u001b[39m, in \u001b[36mTensor.backward\u001b[39m\u001b[34m(self, gradient, retain_graph, create_graph, inputs)\u001b[39m\n\u001b[32m 515\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m has_torch_function_unary(\u001b[38;5;28mself\u001b[39m):\n\u001b[32m 516\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m handle_torch_function(\n\u001b[32m 517\u001b[39m Tensor.backward,\n\u001b[32m 518\u001b[39m (\u001b[38;5;28mself\u001b[39m,),\n\u001b[32m (...)\u001b[39m\u001b[32m 523\u001b[39m inputs=inputs,\n\u001b[32m 524\u001b[39m )\n\u001b[32m--> \u001b[39m\u001b[32m525\u001b[39m \u001b[43mtorch\u001b[49m\u001b[43m.\u001b[49m\u001b[43mautograd\u001b[49m\u001b[43m.\u001b[49m\u001b[43mbackward\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 526\u001b[39m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mgradient\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mretain_graph\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcreate_graph\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minputs\u001b[49m\u001b[43m=\u001b[49m\u001b[43minputs\u001b[49m\n\u001b[32m 527\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n",
|
| 783 |
+
"\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/envs/mini-transformer/lib/python3.11/site-packages/torch/autograd/__init__.py:267\u001b[39m, in \u001b[36mbackward\u001b[39m\u001b[34m(tensors, grad_tensors, retain_graph, create_graph, grad_variables, inputs)\u001b[39m\n\u001b[32m 262\u001b[39m retain_graph = create_graph\n\u001b[32m 264\u001b[39m \u001b[38;5;66;03m# The reason we repeat the same comment below is that\u001b[39;00m\n\u001b[32m 265\u001b[39m \u001b[38;5;66;03m# some Python versions print out the first line of a multi-line function\u001b[39;00m\n\u001b[32m 266\u001b[39m \u001b[38;5;66;03m# calls in the traceback and some print out the last line\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m267\u001b[39m \u001b[43m_engine_run_backward\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 268\u001b[39m \u001b[43m \u001b[49m\u001b[43mtensors\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 269\u001b[39m \u001b[43m \u001b[49m\u001b[43mgrad_tensors_\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 270\u001b[39m \u001b[43m \u001b[49m\u001b[43mretain_graph\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 271\u001b[39m \u001b[43m \u001b[49m\u001b[43mcreate_graph\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 272\u001b[39m \u001b[43m \u001b[49m\u001b[43minputs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 273\u001b[39m \u001b[43m \u001b[49m\u001b[43mallow_unreachable\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 274\u001b[39m \u001b[43m \u001b[49m\u001b[43maccumulate_grad\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 275\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n",
|
| 784 |
+
"\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/envs/mini-transformer/lib/python3.11/site-packages/torch/autograd/graph.py:744\u001b[39m, in \u001b[36m_engine_run_backward\u001b[39m\u001b[34m(t_outputs, *args, **kwargs)\u001b[39m\n\u001b[32m 742\u001b[39m unregister_hooks = _register_logging_hooks_on_whole_graph(t_outputs)\n\u001b[32m 743\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m744\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mVariable\u001b[49m\u001b[43m.\u001b[49m\u001b[43m_execution_engine\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrun_backward\u001b[49m\u001b[43m(\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# Calls into the C++ engine to run the backward pass\u001b[39;49;00m\n\u001b[32m 745\u001b[39m \u001b[43m \u001b[49m\u001b[43mt_outputs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\n\u001b[32m 746\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# Calls into the C++ engine to run the backward pass\u001b[39;00m\n\u001b[32m 747\u001b[39m \u001b[38;5;28;01mfinally\u001b[39;00m:\n\u001b[32m 748\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m attach_logging_hooks:\n",
|
| 785 |
+
"\u001b[31mKeyboardInterrupt\u001b[39m: "
|
| 786 |
]
|
| 787 |
}
|
| 788 |
],
|
|
|
|
| 1058 |
"execution_count": null,
|
| 1059 |
"id": "39f61da8",
|
| 1060 |
"metadata": {},
|
| 1061 |
+
"outputs": [
|
| 1062 |
+
{
|
| 1063 |
+
"name": "stderr",
|
| 1064 |
+
"output_type": "stream",
|
| 1065 |
+
"text": [
|
| 1066 |
+
"Testing: 32%|███▏ | 101/313 [02:04<03:10, 1.11it/s]"
|
| 1067 |
+
]
|
| 1068 |
+
}
|
| 1069 |
+
],
|
| 1070 |
"source": [
|
| 1071 |
+
"# test the model (extended: compute BLEU/chrF/ROUGE, perplexity, exact/token accuracy)\n",
|
| 1072 |
+
"import time\n",
|
| 1073 |
+
"\n",
|
| 1074 |
+
"import evaluate\n",
|
| 1075 |
+
"import torch.nn.functional as F\n",
|
| 1076 |
+
"from tqdm import tqdm\n",
|
| 1077 |
+
"\n",
|
| 1078 |
+
"# select some samples to test\n",
|
| 1079 |
+
"test_dataset = test_dataset.select(range(10000))\n",
|
| 1080 |
+
"test_dataloader = DataLoader(\n",
|
| 1081 |
+
" test_dataset,\n",
|
| 1082 |
+
" batch_size=scfg.trainer.batch_size,\n",
|
| 1083 |
+
" shuffle=False,\n",
|
| 1084 |
+
" collate_fn=tuple_collate_fn,\n",
|
| 1085 |
+
" generator=dataloader_generator,\n",
|
| 1086 |
+
" worker_init_fn=worker_init,\n",
|
| 1087 |
+
" num_workers=num_workers,\n",
|
| 1088 |
+
" pin_memory=pin_memory,\n",
|
| 1089 |
+
" persistent_workers=persistent_workers,\n",
|
| 1090 |
+
")\n",
|
| 1091 |
"model.eval()\n",
|
| 1092 |
"test_loss = 0.0\n",
|
| 1093 |
+
"all_hyps = []\n",
|
| 1094 |
+
"all_refs = []\n",
|
| 1095 |
+
"latencies = []\n",
|
| 1096 |
+
"total_nll = 0.0\n",
|
| 1097 |
+
"total_tokens = 0\n",
|
| 1098 |
+
"exact_matches = 0\n",
|
| 1099 |
+
"token_matches = 0\n",
|
| 1100 |
+
"token_total = 0\n",
|
| 1101 |
+
"\n",
|
| 1102 |
+
"bleu_metric = evaluate.load(\"sacrebleu\")\n",
|
| 1103 |
+
"chrf_metric = evaluate.load(\"chrf\")\n",
|
| 1104 |
+
"rouge_metric = evaluate.load(\"rouge\")\n",
|
| 1105 |
+
"\n",
|
| 1106 |
+
"pad_id = (\n",
|
| 1107 |
+
" getattr(tokenizer_cfg, \"pad_id\", None)\n",
|
| 1108 |
+
" if \"tokenizer_cfg\" in globals()\n",
|
| 1109 |
+
" else getattr(model_cfg, \"pad_id\", None)\n",
|
| 1110 |
+
")\n",
|
| 1111 |
+
"\n",
|
| 1112 |
"with torch.no_grad(), autocast_ctx:\n",
|
| 1113 |
" for src, tgt in tqdm(test_dataloader, desc=\"Testing\"):\n",
|
| 1114 |
+
" # tokenization (src and tgt are lists of strings per tuple_collate_fn)\n",
|
| 1115 |
" encoded_src = tokenizer(\n",
|
| 1116 |
" src,\n",
|
| 1117 |
" padding=True,\n",
|
|
|
|
| 1131 |
" tgt_ids = encoded_tgt[\"input_ids\"].to(device)\n",
|
| 1132 |
" decoder_in = tgt_ids[:, :-1]\n",
|
| 1133 |
" labels = tgt_ids[:, 1:]\n",
|
| 1134 |
+
" tgt_padd_mask = decoder_in.eq(pad_id)\n",
|
| 1135 |
"\n",
|
| 1136 |
+
" # forward for test loss (uses configured criterion)\n",
|
| 1137 |
" logits = model(src_ids, decoder_in, src_padd_mask, tgt_padd_mask)\n",
|
| 1138 |
" loss = criterion(logits.reshape(-1, logits.size(-1)), labels.reshape(-1))\n",
|
| 1139 |
" test_loss += loss.item()\n",
|
| 1140 |
+
"\n",
|
| 1141 |
+
" # compute token-summed NLL for perplexity\n",
|
| 1142 |
+
" V = logits.size(-1)\n",
|
| 1143 |
+
" logits_flat = logits.reshape(-1, V) # Changed from view to reshape\n",
|
| 1144 |
+
" labels_flat = labels.reshape(-1) # Changed from view to reshape\n",
|
| 1145 |
+
" nll_sum = F.cross_entropy(logits_flat, labels_flat, ignore_index=pad_id, reduction=\"sum\")\n",
|
| 1146 |
+
" non_pad = (labels_flat != pad_id).sum().item()\n",
|
| 1147 |
+
" total_nll += nll_sum.item()\n",
|
| 1148 |
+
" total_tokens += non_pad\n",
|
| 1149 |
+
"\n",
|
| 1150 |
+
" # generation and latency\n",
|
| 1151 |
+
" t0 = time.perf_counter()\n",
|
| 1152 |
+
" out_ids = model.generate(\n",
|
| 1153 |
+
" src_ids, src_padd_mask, max_new_tokens=128, temperature=1.0, do_sample=False\n",
|
| 1154 |
+
" )\n",
|
| 1155 |
+
" t1 = time.perf_counter()\n",
|
| 1156 |
+
" latencies.append((t1 - t0) / max(1, src_ids.size(0)))\n",
|
| 1157 |
+
"\n",
|
| 1158 |
+
" hyps = tokenizer.batch_decode(out_ids.cpu(), skip_special_tokens=True)\n",
|
| 1159 |
+
" all_hyps.extend(hyps)\n",
|
| 1160 |
+
" all_refs.extend(tgt)\n",
|
| 1161 |
+
"\n",
|
| 1162 |
+
" # exact match and token-level accuracy\n",
|
| 1163 |
+
" for h, r in zip(hyps, tgt, strict=False):\n",
|
| 1164 |
+
" if h.strip().lower() == r.strip().lower():\n",
|
| 1165 |
+
" exact_matches += 1\n",
|
| 1166 |
+
"\n",
|
| 1167 |
+
" hyp_tok = tokenizer(hyps, padding=True, truncation=True, return_tensors=\"pt\")[\"input_ids\"]\n",
|
| 1168 |
+
" ref_tok = tgt_ids[:, : hyp_tok.size(1)].cpu()\n",
|
| 1169 |
+
" matches = (hyp_tok[:, : ref_tok.size(1)] == ref_tok).cpu()\n",
|
| 1170 |
+
" token_matches += int(matches.sum().item())\n",
|
| 1171 |
+
" token_total += int(ref_tok.numel())\n",
|
| 1172 |
+
"\n",
|
| 1173 |
+
"# aggregate and report\n",
|
| 1174 |
"avg_test_loss = test_loss / max(1, len(test_dataloader))\n",
|
| 1175 |
+
"avg_latency = sum(latencies) / len(latencies) if latencies else None\n",
|
| 1176 |
+
"p95_latency = sorted(latencies)[int(0.95 * len(latencies))] if latencies else None\n",
|
| 1177 |
+
"avg_nll = (total_nll / total_tokens) if total_tokens > 0 else float(\"nan\")\n",
|
| 1178 |
+
"perplexity = math.exp(avg_nll) if total_tokens > 0 and avg_nll < 100 else float(\"inf\")\n",
|
| 1179 |
+
"exact_match_rate = exact_matches / len(all_hyps) if all_hyps else 0.0\n",
|
| 1180 |
+
"token_accuracy = token_matches / token_total if token_total > 0 else 0.0\n",
|
| 1181 |
+
"\n",
|
| 1182 |
+
"# corpus-level metrics\n",
|
| 1183 |
+
"refs_for_metric = [[r] for r in all_refs]\n",
|
| 1184 |
+
"bleu_res = bleu_metric.compute(predictions=all_hyps, references=refs_for_metric)\n",
|
| 1185 |
+
"chrf_res = chrf_metric.compute(predictions=all_hyps, references=refs_for_metric)\n",
|
| 1186 |
+
"rouge_res = rouge_metric.compute(predictions=all_hyps, references=[r[0] for r in refs_for_metric])\n",
|
| 1187 |
+
"\n",
|
| 1188 |
"print(f\"Test Loss: {avg_test_loss:.4f}\")\n",
|
| 1189 |
+
"print(f\"Generated {len(all_hyps)} hypotheses\")\n",
|
| 1190 |
+
"print(f\"Avg latency per sentence: {avg_latency:.4f} s\")\n",
|
| 1191 |
+
"print(f\"p95 latency per sentence: {p95_latency:.4f} s\")\n",
|
| 1192 |
+
"print(f\"Perplexity (corpus): {perplexity:.3f}\")\n",
|
| 1193 |
+
"print(f\"Exact match rate: {exact_match_rate:.3%}\")\n",
|
| 1194 |
+
"print(f\"Token accuracy: {token_accuracy:.3%}\")\n",
|
| 1195 |
+
"print(\"BLEU:\", bleu_res)\n",
|
| 1196 |
+
"print(\"chrF:\", chrf_res)\n",
|
| 1197 |
+
"print(\"ROUGE:\", rouge_res)\n",
|
| 1198 |
+
"\n",
|
| 1199 |
+
"# Prepare test metrics\n",
|
| 1200 |
+
"test_metrics = {\n",
|
| 1201 |
" \"test_loss\": avg_test_loss,\n",
|
| 1202 |
+
" \"perplexity\": perplexity,\n",
|
| 1203 |
+
" \"avg_latency_s\": avg_latency,\n",
|
| 1204 |
+
" \"p95_latency_s\": p95_latency,\n",
|
| 1205 |
+
" \"exact_match_rate\": exact_match_rate,\n",
|
| 1206 |
+
" \"token_accuracy\": token_accuracy,\n",
|
| 1207 |
+
" \"bleu\": bleu_res,\n",
|
| 1208 |
+
" \"chrf\": chrf_res,\n",
|
| 1209 |
+
" \"rouge\": rouge_res,\n",
|
| 1210 |
" \"timestamp\": datetime.now().isoformat(),\n",
|
| 1211 |
+
" \"model_name\": model_cfg.name,\n",
|
| 1212 |
+
" \"dataset_size\": len(test_dataset),\n",
|
| 1213 |
+
" \"checkpoint_path\": str(resume_checkpoint_path) if resume_checkpoint_path else None,\n",
|
| 1214 |
"}\n",
|
| 1215 |
+
"\n",
|
| 1216 |
+
"# Save to manifest\n",
|
| 1217 |
+
"manifest_data[\"test\"] = test_metrics\n",
|
| 1218 |
+
"\n",
|
| 1219 |
+
"# Save metrics to a separate file\n",
|
| 1220 |
+
"metrics_file = run_dir / \"test_metrics.json\"\n",
|
| 1221 |
+
"metrics_file.write_text(json.dumps(test_metrics, indent=2))\n",
|
| 1222 |
+
"print(f\"[saved] Test metrics -> {metrics_file}\")\n",
|
| 1223 |
+
"\n",
|
| 1224 |
"manifest_path.write_text(json.dumps(manifest_data, indent=2))"
|
| 1225 |
]
|
| 1226 |
},
|
pyproject.toml
CHANGED
|
@@ -46,6 +46,14 @@ notebook = [
|
|
| 46 |
"jupyterlab",
|
| 47 |
]
|
| 48 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
[project.scripts]
|
| 51 |
mini-transformer-infer = "mini_transformer.cli:infer_main"
|
|
|
|
| 46 |
"jupyterlab",
|
| 47 |
]
|
| 48 |
|
| 49 |
+
evaluation = [
|
| 50 |
+
"evaluate",
|
| 51 |
+
"sacrebleu",
|
| 52 |
+
"rouge-score",
|
| 53 |
+
"bert-score",
|
| 54 |
+
"tqdm",
|
| 55 |
+
]
|
| 56 |
+
|
| 57 |
|
| 58 |
[project.scripts]
|
| 59 |
mini-transformer-infer = "mini_transformer.cli:infer_main"
|
requirements.txt
CHANGED
|
@@ -9,3 +9,9 @@ huggingface_hub
|
|
| 9 |
chainlit
|
| 10 |
uvicorn[standard]
|
| 11 |
fastapi
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
chainlit
|
| 10 |
uvicorn[standard]
|
| 11 |
fastapi
|
| 12 |
+
|
| 13 |
+
evaluate
|
| 14 |
+
sacrebleu
|
| 15 |
+
rouge-score
|
| 16 |
+
bert-score
|
| 17 |
+
tqdm
|