Skip to content

Commit 5fac540

Browse files
authored
Further tune the run tests script (flagos-ai#2372)
1 parent 13bb29d commit 5fac540

1 file changed

Lines changed: 77 additions & 58 deletions

File tree

tools/run_tests.py

Lines changed: 77 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import shutil
1010
import subprocess
1111
import sys
12-
import threading
1312
from concurrent.futures import ThreadPoolExecutor, as_completed
1413
from decimal import Decimal, getcontext
1514
from importlib import metadata
@@ -23,13 +22,13 @@
2322
getcontext().prec = 18
2423

2524
# Global lock for writing result file and the result file
26-
SUMMARY_LOCK = threading.Lock()
2725
GLOBAL_RESULTS = {}
2826
ENV_INFO = {}
2927
HAS_TRITON = False
3028
HAS_FLAGTREE = False
3129
ROOT = Path(__file__).parent.parent
3230
OUPUT_DIR = None
31+
OP_LIST = []
3332

3433
NO_CPU_LIST = [
3534
"flash_attention_forward",
@@ -216,34 +215,35 @@ def run_cmd_capture(cmd, cwd=None, env=None):
216215

217216

218217
def 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

249249
def 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

559570
def 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

Comments
 (0)