99import shutil
1010import subprocess
1111import sys
12- import threading
1312from concurrent .futures import ThreadPoolExecutor , as_completed
1413from decimal import Decimal , getcontext
1514from importlib import metadata
2322getcontext ().prec = 18
2423
2524# Global lock for writing result file and the result file
26- SUMMARY_LOCK = threading .Lock ()
2725GLOBAL_RESULTS = {}
2826ENV_INFO = {}
2927HAS_TRITON = False
3028HAS_FLAGTREE = False
3129ROOT = Path (__file__ ).parent .parent
3230OUPUT_DIR = None
31+ OP_LIST = []
3332
3433NO_CPU_LIST = [
3534 "flash_attention_forward" ,
@@ -216,34 +215,35 @@ def run_cmd_capture(cmd, cwd=None, env=None):
216215
217216
218217def parse_accuracy_log (text ):
219- counter = {
218+ record = {
220219 "passed" : 0 ,
221220 "failed" : 0 ,
222221 "skipped" : 0 ,
223222 "errors" : 0 ,
224223 "total" : 0 ,
224+ "status" : "" ,
225225 }
226226
227227 clean = ANSI_RE .sub ("" , text )
228228 for m in re .finditer (r"(\d+)\s+([A-Za-z_]+)" , clean ):
229229 num = int (m .group (1 ))
230230 key = m .group (2 ).lower ()
231- if key in counter :
232- counter [key ] = num
233-
234- total = counter ["failed" ] + counter ["passed" ] + counter ["skipped" ]
235- counter ["total" ] = total
236-
237- if counter ["failed" ] > 0 :
238- counter ["status" ] = "FAIL"
239- elif counter ["errors" ] > 0 and total == 0 :
240- counter ["status" ] = "FAIL" # pytest failed to start
241- elif counter ["passed" ] == 0 :
242- counter ["status" ] = "FAIL"
231+ if key in record :
232+ record [key ] = num
233+
234+ total = record ["failed" ] + record ["passed" ] + record ["skipped" ]
235+ record ["total" ] = total
236+
237+ if record ["failed" ] > 0 :
238+ record ["status" ] = "FAIL"
239+ elif record ["errors" ] > 0 and total == 0 :
240+ record ["status" ] = "FAIL" # pytest failed to start
241+ elif record ["passed" ] == 0 :
242+ record ["status" ] = "FAIL"
243243 else :
244- counter ["status" ] = "PASS"
244+ record ["status" ] = "PASS"
245245
246- return counter
246+ return record
247247
248248
249249def get_env (gpu_ids ):
@@ -295,17 +295,21 @@ def dedup(fn):
295295 f .writelines (uniq )
296296
297297
298- def run_accuracy (op , gpu_id , op_dir ):
299- pinfo (f"[GPU { gpu_id :2d} ] Running accuracy tests for '{ op } '" )
298+ def run_accuracy (gpu_id , start , index , count ):
299+ op = OP_LIST [start + index ].strip ()
300+ n = index * 10 // count
301+ prog = "█" * n + " " * (10 - n )
302+ pinfo (f"[GPU { gpu_id :2d} ][{ index :3d} ][{ prog } ] Running accuracy tests for '{ op } '" )
300303 env = get_env (str (gpu_id ))
301304
302- if f" { op } " in NO_CPU_LIST :
305+ if op in NO_CPU_LIST :
303306 cmd = f'pytest -m "{ op } " -vs'
304307 else :
305308 cmd = f'pytest -m "{ op } " --ref cpu -vs'
306309 out , err , code = run_cmd_capture (cmd , cwd = ROOT .joinpath ("tests" ), env = env )
307310
308311 combined = out + "\n " + err
312+ op_dir = OUTPUT_DIR .joinpath (op )
309313 log_file = op_dir .joinpath ("accuracy.log" )
310314 with open (log_file , "w" ) as f :
311315 f .write (combined )
@@ -326,13 +330,11 @@ def parse_perf_log(op_dir):
326330 with perf_log_file .open ("r" ) as f :
327331 lines = f .readlines ()
328332 line_no = 0
333+ data = {}
329334 while line_no < len (lines ):
330335 line = lines [line_no ]
331336 if "deselected / 0 selected" in line :
332- record = {
333- "result" : "NOT TESTED" ,
334- "error" : "No test case." ,
335- }
337+ record = {"status" : "Unkown" , "error" : "No test case." , "data" : {}}
336338 return record
337339
338340 if "FAILED" in line and "Operator" in line and "dtype" in line :
@@ -351,9 +353,10 @@ def parse_perf_log(op_dir):
351353 line = lines [line_no ]
352354 pos2 = line .find (">>>" )
353355 err_str += line [:pos2 ]
354- record .setdefault (dtype , {})
355- record [dtype ].setdefault ("result" , "FAILED" )
356- record [dtype ].setdefault ("error" , err_str )
356+ data [dtype ] = {
357+ "result" : "FAILED" ,
358+ "error" : err_str ,
359+ }
357360 line_no += 1
358361
359362 # Check if there are usable records
@@ -378,7 +381,7 @@ def parse_perf_log(op_dir):
378381 count = 0
379382 # Iterate through shapes
380383 for res in item .get ("result" , []):
381- shape = str (res .get ("shape_detail" , "Unknown " )).replace (" " , "" )
384+ shape = str (res .get ("shape_detail" , "UNKNOWN " )).replace (" " , "" )
382385 details .setdefault (shape , {})
383386 details [shape ]["base" ] = res .get ("latency_base" , 0.0 )
384387 details [shape ]["gems" ] = res .get ("latency" , 0.0 )
@@ -388,27 +391,32 @@ def parse_perf_log(op_dir):
388391 total += speedup
389392
390393 if details :
391- record [dtype ] = {
394+ data [dtype ] = {
392395 "result" : "OK" ,
393396 "details" : details ,
394397 "speedup" : total / count ,
395398 }
396399 else :
397- record [dtype ] = {
398- "result" : "Incomplete " ,
400+ data [dtype ] = {
401+ "result" : "UNKNOWN " ,
399402 "details" : details ,
400403 "speedup" : 0 ,
401404 }
402405
403- return record
406+ return {
407+ "data" : data ,
408+ }
404409
405410
406- def run_benchmark (op , gpu_id , op_dir ):
411+ def run_benchmark (gpu_id , start , index , count ):
407412 """Run benchmark for a specific operator on a specific GPU/DCU.
408413
409414 This returns a dict as report summary.
410415 """
411- pinfo (f"[GPU { gpu_id :2d} ] Running performance benchmark for '{ op } '" )
416+ op = OP_LIST [start + index ].strip ()
417+ n = index * 10 // count
418+ prog = "█" * n + " " * (10 - n )
419+ pinfo (f"[GPU { gpu_id :2d} ][{ index :3d} ][{ prog } Running perf benchmark for '{ op } '" )
412420
413421 env = get_env (str (gpu_id ))
414422
@@ -424,6 +432,7 @@ def run_benchmark(op, gpu_id, op_dir):
424432 out , err , code = run_cmd_capture (cmd , cwd = benchmark_dir , env = env )
425433
426434 # Write raw command output
435+ op_dir = OUTPUT_DIR .joinpath (op )
427436 output_file = op_dir .joinpath ("performance_output.log" )
428437 with open (output_file , "w" ) as f :
429438 f .write (out + "\n ---\n " + err )
@@ -437,9 +446,10 @@ def run_benchmark(op, gpu_id, op_dir):
437446 # Not found
438447 if not result_file :
439448 return {
440- "status" : "NO_RESULT " ,
449+ "status" : "No Result " ,
441450 "log" : str (output_file .relative_to (OUTPUT_DIR )),
442451 "result" : None ,
452+ "exit_code" : code ,
443453 "data" : [],
444454 }
445455
@@ -451,28 +461,29 @@ def run_benchmark(op, gpu_id, op_dir):
451461 # Remove duplicate lines in the result file
452462 dedup (result_file )
453463
454- perf_result = parse_perf_log (op_dir )
455-
456- return {
464+ record = {
457465 "status" : "OK" ,
466+ "exit_code" : code ,
458467 "log" : str (output_file .relative_to (OUTPUT_DIR )),
459- "result" : str (result_file .relative_to (OUTPUT_DIR )),
460- "data" : perf_result ,
468+ "result_file" : str (result_file .relative_to (OUTPUT_DIR )),
461469 }
470+ record .update (parse_perf_log (op_dir ))
462471
472+ return record
463473
464- def worker_proc (gpu_id , ops_list ):
474+
475+ def worker_proc (gpu_id , start , count ):
465476 worker_result = {}
466- for op in ops_list :
467- op = op .strip ()
477+ for i in range ( count ) :
478+ op = OP_LIST [ start + i ] .strip ()
468479 if not op :
469480 continue
470481
471482 op_dir = OUTPUT_DIR .joinpath (op )
472483 ensure_dir (op_dir )
473484
474- acc = run_accuracy (op , gpu_id , op_dir )
475- perf = run_benchmark (op , gpu_id , op_dir )
485+ acc = run_accuracy (gpu_id , start , i , count )
486+ perf = run_benchmark (gpu_id , start , i , count )
476487
477488 result = {
478489 "accuracy" : acc ,
@@ -558,6 +569,7 @@ def write_xlsx(path):
558569
559570def main ():
560571 global OUTPUT_DIR
572+ global OP_LIST
561573
562574 init ()
563575 op_catalog = get_ops ()
@@ -588,11 +600,13 @@ def main():
588600
589601 ops = [ln .strip () for ln in lines if ln .strip () and not ln .startswith ("#" )]
590602
591- if len (ops ) == 0 :
603+ OP_LIST = ops
604+ op_count = len (ops )
605+ if op_count == 0 :
592606 pwarn ("No operators to test. Please specify at lease one operator." )
593607 sys .exit (1 )
594608 else :
595- pinfo (f"Testing { len ( ops ) } operators ..." )
609+ pinfo (f"Testing { op_count } operators ..." )
596610
597611 now_ts = datetime .datetime .now ().strftime ("%Y%m%d_%H%M%S" )
598612 OUTPUT_DIR = ROOT .joinpath (f"results_{ now_ts } " )
@@ -603,17 +617,22 @@ def main():
603617 # Split the operators among GPUs
604618 gpu_ids = [int (x ) for x in args .gpus .split ("," ) if x .strip ()]
605619 gpu_count = len (gpu_ids )
606- tasks = {gpu_id : [] for gpu_id in gpu_ids }
607- for i , op in enumerate (ops ):
608- tasks [gpu_ids [i % gpu_count ]].append (op )
609-
610- with ThreadPoolExecutor (max_workers = gpu_count ) as exe :
611- futures = []
612- for gpu in gpu_ids :
613- if tasks [gpu ]:
614- futures .append (exe .submit (worker_proc , gpu , tasks [gpu ]))
615- for f in as_completed (futures ):
616- f .result ()
620+ if gpu_count == 1 :
621+ worker_proc (gpu_ids [0 ], 0 , op_count )
622+ else :
623+ with ThreadPoolExecutor (max_workers = gpu_count ) as exe :
624+ futures = []
625+ m , n = divmod (op_count , gpu_count )
626+ start = 0
627+ for i , gpu in enumerate (gpu_ids ):
628+ if i < n :
629+ count = m + 1
630+ else :
631+ count = m
632+ futures .append (exe .submit (worker_proc , gpu , start , count ))
633+ start += count
634+ for f in as_completed (futures ):
635+ f .result ()
617636
618637 timestamp = datetime .datetime .now ().strftime ("%Y-%m-%d %H:%M:%S" )
619638 op_data = {}
0 commit comments