AlaBoussoffara commited on
Commit
4f973e3
·
1 Parent(s): c887602

added evaluation metrics & evaluated small model

Browse files
Files changed (5) hide show
  1. README.md +18 -0
  2. environment.yml +5 -0
  3. notebooks/train.ipynb +153 -11
  4. pyproject.toml +8 -0
  5. 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=medium_model\",\n",
107
- " \"tokenizer=bpe_16k\",\n",
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: 184705024\n"
225
  ]
226
  }
227
  ],
@@ -444,7 +446,7 @@
444
  "name": "stdout",
445
  "output_type": "stream",
446
  "text": [
447
- "Starting fresh (no resume checkpoint provided).\n"
448
  ]
449
  }
450
  ],
@@ -757,7 +759,7 @@
757
  },
758
  {
759
  "cell_type": "code",
760
- "execution_count": null,
761
  "id": "439d4094",
762
  "metadata": {},
763
  "outputs": [
@@ -765,7 +767,22 @@
765
  "name": "stderr",
766
  "output_type": "stream",
767
  "text": [
768
- "Epoch 1 [train]: 2%|| 1380/57063 [10:36<6:56:33, 2.23it/s]"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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(scfg.tokenizer.pad_id)\n",
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
- "# save test loss to the manifest\n",
1078
- "manifest_data[\"test\"] = {\n",
 
 
 
 
 
 
 
 
 
 
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