Cyprien Claude Opus 5 (1M context) commited on
Commit
b64175d
·
1 Parent(s): 759bf41

Batch the evaluation so progress actually streams

Browse files

The handler emitted one progress event then blocked in a single predict()
over the whole split, so the UI froze. Inference now runs in batches and
evaluation.run takes a track() wrapper; only handlers knows it is
progress.tqdm. Batched output matches the single call to 6e-8.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Files changed (5) hide show
  1. README.md +4 -3
  2. app/evaluation.py +28 -2
  3. app/handlers.py +7 -3
  4. app/text.py +2 -0
  5. app/ui.py +11 -4
README.md CHANGED
@@ -27,8 +27,9 @@ Publié par `train.py` dans
27
  [`bee2link/breakdown-risk-paraphrase-multilingual-MiniLM-L12-v2`](https://huggingface.co/bee2link/breakdown-risk-paraphrase-multilingual-MiniLM-L12-v2),
28
  entraîné sur [`bee2link/breakdown-risk`](https://huggingface.co/datasets/bee2link/breakdown-risk).
29
 
30
- 45/46 sur le split de test. Les exemples de la démo viennent de ce même split :
31
- ce sont des tours que le modèle n'a jamais vus à l'entraînement.
 
32
 
33
  ## La fenêtre de trois tours
34
 
@@ -95,7 +96,7 @@ uv lock # après toute modification de pyproject.toml
95
  d'une régression logistique picklée, et `app.py` charge ces deux pièces
96
  directement — `gradio` 6 exige `transformers>=5`, que `setfit` 1.1 ne supporte
97
  pas (`ImportError: default_logdir`). Les deux chemins donnent les mêmes
98
- prédictions : 45/46 sur le split de test.
99
 
100
  `torch` vient de l'index CPU sur Linux, ce qui évite ~2 Go de CUDA inutile dans
101
  l'image.
 
27
  [`bee2link/breakdown-risk-paraphrase-multilingual-MiniLM-L12-v2`](https://huggingface.co/bee2link/breakdown-risk-paraphrase-multilingual-MiniLM-L12-v2),
28
  entraîné sur [`bee2link/breakdown-risk`](https://huggingface.co/datasets/bee2link/breakdown-risk).
29
 
30
+ L'onglet **Évaluation** donne le score courant : le split de test grandit, donc
31
+ aucun chiffre n'est recopié ici. Les exemples de la démo viennent de ce même
32
+ split — des tours que le modèle n'a jamais vus à l'entraînement.
33
 
34
  ## La fenêtre de trois tours
35
 
 
96
  d'une régression logistique picklée, et `app.py` charge ces deux pièces
97
  directement — `gradio` 6 exige `transformers>=5`, que `setfit` 1.1 ne supporte
98
  pas (`ImportError: default_logdir`). Les deux chemins donnent les mêmes
99
+ prédictions, à 6e-8 près.
100
 
101
  `torch` vient de l'index CPU sur Linux, ce qui évite ~2 Go de CUDA inutile dans
102
  l'image.
app/evaluation.py CHANGED
@@ -1,4 +1,5 @@
1
  import time
 
2
  from functools import lru_cache
3
  from typing import NamedTuple
4
 
@@ -8,6 +9,10 @@ from datasets import load_dataset
8
  from .config import DATASET_REPO, TOKEN, display
9
  from .predictors import Predictor, load
10
 
 
 
 
 
11
 
12
  class Report(NamedTuple):
13
  repo: str
@@ -50,12 +55,33 @@ def cases(ids, texts, gold, predicted, probabilities: np.ndarray) -> list[list]:
50
  return sorted(rows, key=lambda row: (row[0] == "✓", row[4]))
51
 
52
 
53
- def run(repo: str, revision: str, predict: Predictor | None = None) -> Report:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  predictor = predict if predict is not None else load(repo, revision)
55
  ids, texts, gold = test_split()
56
 
57
  started = time.perf_counter()
58
- labels, probabilities = predictor(texts)
59
  elapsed = time.perf_counter() - started
60
 
61
  predicted = [labels[int(row.argmax())] for row in probabilities]
 
1
  import time
2
+ from collections.abc import Callable, Iterable
3
  from functools import lru_cache
4
  from typing import NamedTuple
5
 
 
9
  from .config import DATASET_REPO, TOKEN, display
10
  from .predictors import Predictor, load
11
 
12
+ BATCH_SIZE = 8
13
+
14
+ Track = Callable[[Iterable], Iterable]
15
+
16
 
17
  class Report(NamedTuple):
18
  repo: str
 
55
  return sorted(rows, key=lambda row: (row[0] == "✓", row[4]))
56
 
57
 
58
+ def batches(texts: list[str], size: int = BATCH_SIZE) -> list[list[str]]:
59
+ return [texts[start : start + size] for start in range(0, len(texts), size)]
60
+
61
+
62
+ def classify_all(
63
+ predictor: Predictor, texts: list[str], track: Track | None = None
64
+ ) -> tuple[list[str], np.ndarray]:
65
+ chunks = batches(texts)
66
+ labels: list[str] = []
67
+ scored = []
68
+ for batch in track(chunks) if track else chunks:
69
+ labels, probabilities = predictor(batch)
70
+ scored.append(probabilities)
71
+ return labels, np.concatenate(scored)
72
+
73
+
74
+ def run(
75
+ repo: str,
76
+ revision: str,
77
+ predict: Predictor | None = None,
78
+ track: Track | None = None,
79
+ ) -> Report:
80
  predictor = predict if predict is not None else load(repo, revision)
81
  ids, texts, gold = test_split()
82
 
83
  started = time.perf_counter()
84
+ labels, probabilities = classify_all(predictor, texts, track)
85
  elapsed = time.perf_counter() - started
86
 
87
  predicted = [labels[int(row.argmax())] for row in probabilities]
app/handlers.py CHANGED
@@ -1,5 +1,6 @@
1
  import json
2
  import time
 
3
  from pathlib import Path
4
 
5
  import gradio as gr
@@ -8,7 +9,7 @@ from . import evaluation
8
  from .config import display
9
  from .hub import model_repos, revisions
10
  from .predictors import load
11
- from .text import NO_MODEL
12
  from .turns import window
13
 
14
  EXAMPLES = json.loads((Path(__file__).parent / "examples.json").read_text(encoding="utf-8"))
@@ -37,8 +38,11 @@ def evaluate(repo: str, revision: str, progress=gr.Progress()):
37
  if not repo:
38
  return NO_MODEL, [], []
39
 
40
- progress(0, desc="Chargement du modèle")
41
- report = evaluation.run(repo, revision)
 
 
 
42
  return summary(report), report.confusion, report.cases
43
 
44
 
 
1
  import json
2
  import time
3
+ from functools import partial
4
  from pathlib import Path
5
 
6
  import gradio as gr
 
9
  from .config import display
10
  from .hub import model_repos, revisions
11
  from .predictors import load
12
+ from .text import LOADING, NO_MODEL, SCORING
13
  from .turns import window
14
 
15
  EXAMPLES = json.loads((Path(__file__).parent / "examples.json").read_text(encoding="utf-8"))
 
38
  if not repo:
39
  return NO_MODEL, [], []
40
 
41
+ progress(0, desc=LOADING)
42
+ predictor = load(repo, revision)
43
+ track = partial(progress.tqdm, desc=SCORING)
44
+
45
+ report = evaluation.run(repo, revision, predict=predictor, track=track)
46
  return summary(report), report.confusion, report.cases
47
 
48
 
app/text.py CHANGED
@@ -20,3 +20,5 @@ TRANSCRIPT_INFO = (
20
  )
21
 
22
  NO_MODEL = "Aucun modèle sélectionné."
 
 
 
20
  )
21
 
22
  NO_MODEL = "Aucun modèle sélectionné."
23
+ LOADING = "Chargement du modèle"
24
+ SCORING = "Classement du split de test"
app/ui.py CHANGED
@@ -55,7 +55,7 @@ def classify_tab() -> tuple[gr.Textbox, gr.Button, list]:
55
  return transcript, run, [prediction, reads, latency]
56
 
57
 
58
- def evaluation_tab() -> tuple[gr.Button, list]:
59
  run = gr.Button("Évaluer sur le split de test", variant="primary")
60
  score = gr.Markdown()
61
  matrix = gr.Dataframe(
@@ -70,7 +70,7 @@ def evaluation_tab() -> tuple[gr.Button, list]:
70
  wrap=True,
71
  interactive=False,
72
  )
73
- return run, [score, matrix, cases]
74
 
75
 
76
  def build() -> gr.Blocks:
@@ -85,7 +85,7 @@ def build() -> gr.Blocks:
85
  transcript, run, results = classify_tab()
86
 
87
  with gr.Tab("Évaluation"):
88
- evaluate_button, report = evaluation_tab()
89
 
90
  model.change(pick_revision, model, revision)
91
  refresh.click(pick_model, None, model)
@@ -96,6 +96,13 @@ def build() -> gr.Blocks:
96
  results,
97
  api_name="classify",
98
  )
99
- evaluate_button.click(evaluate, selection, report, api_name="evaluate")
 
 
 
 
 
 
 
100
 
101
  return demo
 
55
  return transcript, run, [prediction, reads, latency]
56
 
57
 
58
+ def evaluation_tab() -> tuple[gr.Button, list, gr.Dataframe]:
59
  run = gr.Button("Évaluer sur le split de test", variant="primary")
60
  score = gr.Markdown()
61
  matrix = gr.Dataframe(
 
70
  wrap=True,
71
  interactive=False,
72
  )
73
+ return run, [score, matrix, cases], cases
74
 
75
 
76
  def build() -> gr.Blocks:
 
85
  transcript, run, results = classify_tab()
86
 
87
  with gr.Tab("Évaluation"):
88
+ evaluate_button, report, progress_target = evaluation_tab()
89
 
90
  model.change(pick_revision, model, revision)
91
  refresh.click(pick_model, None, model)
 
96
  results,
97
  api_name="classify",
98
  )
99
+ evaluate_button.click(
100
+ evaluate,
101
+ selection,
102
+ report,
103
+ show_progress="full",
104
+ show_progress_on=progress_target,
105
+ api_name="evaluate",
106
+ )
107
 
108
  return demo