Skip to content

Commit 878a3c2

Browse files
tianxiao-baaiwithdrawn919
authored andcommitted
Enhance run_cmd to log output and load KernelGen ops (flagos-ai#2935)
* Enhance run_cmd to log output and load KernelGen ops Added functionality to log stdout and stderr to separate files in run_cmd function. Also ensured KERNELGEN_OPS is populated in subprocesses. Signed-off-by: tianxiao <tianxiao@baai.ac.cn> * Refactor kernelgen ops to use operator labels Signed-off-by: tianxiao <tianxiao@baai.ac.cn> * Introduce DUMP_OUTPUT option for test logging Added DUMP_OUTPUT flag to control logging of stdout/stderr. Signed-off-by: tianxiao <tianxiao@baai.ac.cn> * Refactor run_tests.py for clarity and functionality Removed OP_LABELS Signed-off-by: tianxiao <tianxiao@baai.ac.cn> * Add script to inject operator labels into summary.json This script adds operator labels from operators.yaml into summary.json, updating each operator entry with the corresponding labels. Signed-off-by: tianxiao <tianxiao@baai.ac.cn> * bug fix Removed the predefined NO_CPU_LIST and updated the logic to build it dynamically based on operator labels. Signed-off-by: tianxiao <tianxiao@baai.ac.cn> * Update run_tests.py Signed-off-by: tianxiao <tianxiao@baai.ac.cn> * Fix formatting Signed-off-by: tianxiao <tianxiao@baai.ac.cn> * code format Signed-off-by: tianxiao <tianxiao@baai.ac.cn> * code format Signed-off-by: tianxiao <tianxiao@baai.ac.cn> --------- Signed-off-by: tianxiao <tianxiao@baai.ac.cn>
1 parent e41128d commit 878a3c2

2 files changed

Lines changed: 196 additions & 13 deletions

File tree

tools/add_labels.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
"""
4+
Standalone script to add operator labels from operators.yaml into summary.json.
5+
6+
Usage:
7+
python add_labels.py <result_dir>
8+
9+
Where <result_dir> is the output directory produced by run_tests.py containing
10+
summary.json. The script reads conf/operators.yaml relative to the project root,
11+
builds a mapping of op_id -> labels, and injects the "labels" field into each
12+
operator entry in summary.json.
13+
"""
14+
import argparse
15+
import json
16+
import sys
17+
from pathlib import Path
18+
19+
import yaml
20+
21+
ROOT = Path(__file__).parent.parent
22+
23+
24+
def pinfo(msg):
25+
print(f"\033[32m[INFO]\033[0m {msg}")
26+
27+
28+
def perror(msg):
29+
print(f"\033[31m[ERROR]\033[0m {msg}")
30+
31+
32+
def load_op_labels():
33+
"""Load operator labels from conf/operators.yaml."""
34+
op_labels = {}
35+
op_inventory = ROOT / "conf" / "operators.yaml"
36+
try:
37+
with open(str(op_inventory), "r") as f:
38+
data = yaml.safe_load(f)
39+
catalog = data.get("ops", [])
40+
except Exception as e:
41+
perror(f"Failed to load operator inventory: {e}")
42+
return op_labels
43+
44+
for op_entry in catalog:
45+
op_id = op_entry.get("id", "")
46+
labels = op_entry.get("labels", [])
47+
op_labels[op_id] = labels
48+
49+
return op_labels
50+
51+
52+
def main():
53+
parser = argparse.ArgumentParser(
54+
description="Add operator labels from operators.yaml into summary.json"
55+
)
56+
parser.add_argument(
57+
"result_dir",
58+
help="Path to the result directory containing summary.json",
59+
)
60+
args = parser.parse_args()
61+
62+
result_dir = Path(args.result_dir)
63+
summary_path = result_dir / "summary.json"
64+
65+
if not summary_path.exists():
66+
perror(f"summary.json not found in {result_dir}")
67+
sys.exit(1)
68+
69+
# Load summary
70+
with summary_path.open("r") as f:
71+
summary = json.load(f)
72+
73+
op_data = summary.get("result", {})
74+
if not op_data:
75+
perror("No 'result' field found in summary.json")
76+
sys.exit(1)
77+
78+
# Load labels from operators.yaml
79+
op_labels = load_op_labels()
80+
if not op_labels:
81+
perror("No labels loaded from operators.yaml")
82+
sys.exit(1)
83+
84+
# Inject labels into each operator entry
85+
updated = 0
86+
for op_id, op_entry in op_data.items():
87+
labels = op_labels.get(op_id, [])
88+
op_entry["labels"] = labels
89+
updated += 1
90+
91+
# Write back
92+
with summary_path.open("w") as f:
93+
json.dump(summary, f, indent=2)
94+
95+
pinfo(f"Updated {updated} operators with labels in {summary_path}")
96+
97+
98+
if __name__ == "__main__":
99+
main()

tools/run_tests.py

Lines changed: 97 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
ROOT = Path(__file__).parent.parent
3434
OUPUT_DIR = None
3535
OP_LIST = []
36+
DUMP_OUTPUT = False
3637
TIMEOUT = -100
3738
# A list of operators that can only run on GPU/DCUs
3839
NO_CPU_LIST = []
@@ -159,6 +160,7 @@ def init():
159160
repo = git.Repo(search_parent_directories=True)
160161
sha = repo.head.object.hexsha
161162
pinfo(f"flag_gems detected ... {version}+git{sha[:8]}")
163+
ENV_INFO["flag_gems"]["commit_id"] = sha
162164
except RuntimeError as e:
163165
perror(f"{e}")
164166
sys.exit(-1)
@@ -188,21 +190,74 @@ def init():
188190
sys.exit(-1)
189191

190192

191-
def run_cmd(cmd, cwd=None, env=None, timeout=600):
193+
def run_cmd(
194+
cmd,
195+
cwd=None,
196+
env=None,
197+
timeout=600,
198+
stdout_file=None,
199+
stderr_file=None,
200+
):
201+
"""
202+
Safe subprocess runner:
203+
- No PIPE (avoid deadlock)
204+
- Support timeout
205+
- Kill full process group
206+
- Persist stdout/stderr to file
207+
"""
208+
209+
stdout_fh = None
210+
stderr_fh = None
211+
192212
try:
213+
if stdout_file:
214+
stdout_fh = open(stdout_file, "w", buffering=1)
215+
if stderr_file:
216+
stderr_fh = open(stderr_file, "w", buffering=1)
217+
218+
stdout_target = stdout_fh if stdout_fh else subprocess.DEVNULL
219+
stderr_target = stderr_fh if stderr_fh else subprocess.DEVNULL
220+
193221
p = subprocess.Popen(
194222
shlex.split(cmd),
195-
cwd=str(cwd),
223+
cwd=str(cwd) if cwd else None,
196224
env=env,
197-
stdout=subprocess.DEVNULL,
198-
stderr=subprocess.DEVNULL,
225+
stdout=stdout_target,
226+
stderr=stderr_target,
199227
start_new_session=True,
200228
)
201-
p.wait(timeout=timeout)
202-
except subprocess.TimeoutExpired:
203-
os.killpg(os.getpgid(p.pid), signal.SIGTERM)
204-
return TIMEOUT
205-
return p.returncode
229+
230+
try:
231+
p.wait(timeout=timeout)
232+
except subprocess.TimeoutExpired:
233+
try:
234+
os.killpg(p.pid, signal.SIGTERM)
235+
except Exception:
236+
p.terminate()
237+
238+
try:
239+
p.wait(timeout=5)
240+
except subprocess.TimeoutExpired:
241+
try:
242+
os.killpg(p.pid, signal.SIGKILL)
243+
except Exception:
244+
p.kill()
245+
246+
return TIMEOUT
247+
248+
return p.returncode
249+
250+
except Exception as e:
251+
perror(f"run_cmd failed: {e}")
252+
return -1
253+
254+
finally:
255+
if stdout_fh:
256+
stdout_fh.flush()
257+
stdout_fh.close()
258+
if stderr_fh:
259+
stderr_fh.flush()
260+
stderr_fh.close()
206261

207262

208263
def parse_accuracy_data(result_file):
@@ -329,8 +384,19 @@ def run_accuracy(gpu_id, start, index, count):
329384
if result_file.exists():
330385
result_file.unlink()
331386

387+
op_dir = OUTPUT_DIR.joinpath(op)
388+
ensure_dir(op_dir)
389+
stdout_log = str(op_dir / f"accuracy_{op}_stdout.log") if DUMP_OUTPUT else None
390+
stderr_log = str(op_dir / f"accuracy_{op}_stderr.log") if DUMP_OUTPUT else None
391+
332392
start = time.time()
333-
code = run_cmd(cmd, cwd=accuracy_dir, env=env)
393+
code = run_cmd(
394+
cmd,
395+
cwd=accuracy_dir,
396+
env=env,
397+
stdout_file=stdout_log,
398+
stderr_file=stderr_log,
399+
)
334400
end = time.time()
335401

336402
if code == TIMEOUT: # Timeout
@@ -344,7 +410,6 @@ def run_accuracy(gpu_id, start, index, count):
344410
"errors": 0,
345411
"duration": end - start,
346412
}
347-
348413
# There are rare cases where the pytest process aborts
349414
# with no result file generated.
350415
if not result_file.exists():
@@ -451,9 +516,20 @@ def run_benchmark(gpu_id, start, index, count):
451516
if result_file.exists():
452517
result_file.unlink()
453518

519+
op_dir = OUTPUT_DIR.joinpath(op)
520+
ensure_dir(op_dir)
521+
stdout_log = str(op_dir / f"performance_{op}_stdout.log") if DUMP_OUTPUT else None
522+
stderr_log = str(op_dir / f"performance_{op}_stderr.log") if DUMP_OUTPUT else None
523+
454524
start = time.time()
455525
cmd = f'pytest -m "{op}" --level core --record json --output benchmark_{op}.json'
456-
code = run_cmd(cmd, cwd=benchmark_dir, env=env)
526+
code = run_cmd(
527+
cmd,
528+
cwd=benchmark_dir,
529+
env=env,
530+
stdout_file=stdout_log,
531+
stderr_file=stderr_log,
532+
)
457533
end = time.time()
458534

459535
# Not found
@@ -465,7 +541,6 @@ def run_benchmark(gpu_id, start, index, count):
465541
}
466542

467543
# Move record log to output directory
468-
op_dir = OUTPUT_DIR.joinpath(op)
469544
dest = op_dir / "performance_result.json"
470545
shutil.move(result_file, str(dest))
471546
result_file = dest
@@ -583,15 +658,24 @@ def get_ops_to_test(ops_file, ops_list, stages):
583658
def main():
584659
global OUTPUT_DIR
585660
global OP_LIST
661+
global DUMP_OUTPUT
586662

587663
parser = argparse.ArgumentParser()
588664
parser.add_argument("--op-list-file", required=False)
589665
parser.add_argument("--ops", required=False)
590666
parser.add_argument("--gpus", default="0")
591667
parser.add_argument("--output-dir", default=None)
592668
parser.add_argument("--stages", required=False, default="stable")
669+
parser.add_argument(
670+
"--dump-output",
671+
action="store_true",
672+
default=False,
673+
help="Dump stdout/stderr of each test to log files",
674+
)
593675
args = parser.parse_args()
594676

677+
DUMP_OUTPUT = args.dump_output
678+
595679
# Probe environment setttings
596680
init()
597681

0 commit comments

Comments
 (0)