33import logging
44import math
55from collections import defaultdict
6- from concurrent .futures import Executor , Future , ProcessPoolExecutor , as_completed
6+ from concurrent .futures import (
7+ FIRST_COMPLETED ,
8+ Executor ,
9+ Future ,
10+ ProcessPoolExecutor ,
11+ as_completed ,
12+ wait ,
13+ )
714from enum import Enum
815from pathlib import Path
916from typing import Dict , List , Optional , Tuple , Union
@@ -276,10 +283,13 @@ def __call__(
276283 []
277284 ) # Gather f1 score/image when evaluated on collapsed classes
278285
286+ # Keep at most 2× workers in-flight: enough to saturate the pool while
287+ # preventing unbounded accumulation of large uint64 page arrays in memory.
288+ max_pending = self ._concurrency * 2
289+
279290 with ProcessPoolExecutor (max_workers = self ._concurrency ) as executor :
280- futures : list [Future ] = []
291+ pending : set [Future ] = set ()
281292
282- # Submit pages for execution
283293 _log .info ("Submitting the documents for evaluation..." )
284294 for data in ds_selection :
285295 data_record = DatasetRecordWithPrediction .model_validate (data )
@@ -320,43 +330,37 @@ def __call__(
320330 continue
321331
322332 evaluated_samples += 1
323- futures . extend (
333+ pending . update (
324334 self ._submit_document_evaluation (
325335 executor , doc_id , true_doc , pred_doc
326336 )
327337 )
328338
329- # Collect the futures
330- _log .info ("Collecting the documents for evaluations..." )
339+ # Drain completed futures whenever the window is full
340+ while len (pending ) >= max_pending :
341+ done , pending = wait (pending , return_when = FIRST_COMPLETED )
342+ for f in done :
343+ ds_num_pixels += self ._collect_page_result (
344+ f ,
345+ ds_confusion_matrix ,
346+ all_pages_evaluations ,
347+ pages_detailed_f1 ,
348+ pages_collapsed_f1 ,
349+ )
350+
351+ _log .info ("Collecting %d remaining page evaluations..." , len (pending ))
331352 for future in tqdm (
332- as_completed (futures ),
353+ as_completed (pending ),
333354 desc = "Multi-label Matrix Layout evaluations" ,
334355 ncols = 120 ,
335- total = len (futures ),
356+ total = len (pending ),
336357 ):
337- page_metrics : MultiLabelMatrixEvaluation
338- doc_id , page_no , page_pixels , page_metrics = future .result ()
339-
340- page_confusion_matrix : np .ndarray = (
341- page_metrics .detailed .confusion_matrix
342- )
343- ds_num_pixels += page_pixels
344- ds_confusion_matrix += page_confusion_matrix
345- doc_page_id = f"{ doc_id } -{ page_no } "
346- page_evaluation = PagePixelLayoutEvaluation (
347- doc_id = doc_id ,
348- page_no = page_no ,
349- num_pixels = page_pixels ,
350- matrix_evaluation = page_metrics ,
351- )
352- all_pages_evaluations [doc_page_id ] = page_evaluation
353-
354- # Update f1 lists
355- pages_detailed_f1 .append (
356- page_metrics .detailed .agg_metrics .classes_f1_mean
357- )
358- pages_collapsed_f1 .append (
359- page_metrics .collapsed .agg_metrics .classes_f1_mean
358+ ds_num_pixels += self ._collect_page_result (
359+ future ,
360+ ds_confusion_matrix ,
361+ all_pages_evaluations ,
362+ pages_detailed_f1 ,
363+ pages_collapsed_f1 ,
360364 )
361365
362366 # Compute metrics for the dataset
@@ -379,6 +383,31 @@ def __call__(
379383
380384 return ds_evaluation
381385
386+ @staticmethod
387+ def _collect_page_result (
388+ future : Future ,
389+ ds_confusion_matrix : np .ndarray ,
390+ all_pages_evaluations : Dict [str , "PagePixelLayoutEvaluation" ],
391+ pages_detailed_f1 : List [float ],
392+ pages_collapsed_f1 : List [float ],
393+ ) -> int :
394+ """Unpack one completed page future into the shared accumulators.
395+
396+ Returns the number of pixels in the page so the caller can accumulate
397+ ``ds_num_pixels`` without needing access to the internal state.
398+ """
399+ doc_id , page_no , page_pixels , page_metrics = future .result ()
400+ ds_confusion_matrix [:] += page_metrics .detailed .confusion_matrix
401+ all_pages_evaluations [f"{ doc_id } -{ page_no } " ] = PagePixelLayoutEvaluation (
402+ doc_id = doc_id ,
403+ page_no = page_no ,
404+ num_pixels = page_pixels ,
405+ matrix_evaluation = page_metrics ,
406+ )
407+ pages_detailed_f1 .append (page_metrics .detailed .agg_metrics .classes_f1_mean )
408+ pages_collapsed_f1 .append (page_metrics .collapsed .agg_metrics .classes_f1_mean )
409+ return page_pixels
410+
382411 def save_evaluations (
383412 self ,
384413 benchmark : BenchMarkNames ,
0 commit comments