Skip to content

Commit aceab06

Browse files
Merge pull request #183 from KernelTuner/output_file_writer
Output file writer
2 parents 588d93f + 2926d23 commit aceab06

File tree

10 files changed

+806
-92
lines changed

10 files changed

+806
-92
lines changed

kernel_tuner/file_utils.py

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import os
2+
import json
3+
import subprocess
4+
import xmltodict
5+
6+
from importlib.metadata import requires, version, PackageNotFoundError
7+
from packaging.requirements import Requirement
8+
9+
from jsonschema import validate
10+
11+
from kernel_tuner import util
12+
13+
schema_dir = os.path.dirname(os.path.realpath(__file__)) + "/schema"
14+
15+
16+
def output_file_schema(target):
17+
""" Get the requested JSON schema and the version number
18+
19+
:param target: Name of the T4 schema to return, should be any of ['output', 'metadata']
20+
:type target: string
21+
22+
:returns: the current version of the T4 schemas and the JSON string of the target schema
23+
:rtype: string, string
24+
25+
"""
26+
current_version = "1.0.0"
27+
output_file = schema_dir + f"/T4/{current_version}/{target}-schema.json"
28+
with open(output_file, 'r') as fh:
29+
json_string = json.load(fh)
30+
return current_version, json_string
31+
32+
33+
def get_configuration_validity(objective) -> str:
34+
""" Convert internal Kernel Tuner error to string """
35+
errorstring: str
36+
if not isinstance(objective, util.ErrorConfig):
37+
errorstring = "correct"
38+
else:
39+
if isinstance(objective, util.CompilationFailedConfig):
40+
errorstring = "compile"
41+
elif isinstance(objective, util.RuntimeFailedConfig):
42+
errorstring = "runtime"
43+
else:
44+
errorstring = "constraints"
45+
return errorstring
46+
47+
48+
def store_output_file(output_filename, results, tune_params, objective="time"):
49+
""" Store the obtained auto-tuning results in a JSON output file
50+
51+
This function produces a JSON file that adheres to the T4 auto-tuning output JSON schema.
52+
53+
:param output_filename: Name of the to be created output file
54+
:type output_filename: string
55+
56+
:param results: Results list as return by tune_kernel
57+
:type results: list of dicts
58+
59+
:param tune_params: Tunable parameters as passed to tune_kernel
60+
:type tune_params: OrderedDict
61+
62+
:param objective: The objective used during auto-tuning, default is 'time'.
63+
:type objective: string
64+
65+
"""
66+
if output_filename[-5:] != ".json":
67+
output_filename += ".json"
68+
69+
timing_keys = [
70+
"compile_time", "benchmark_time", "framework_time", "strategy_time",
71+
"verification_time"
72+
]
73+
not_measurement_keys = list(
74+
tune_params.keys()) + timing_keys + ["timestamp"] + ["times"]
75+
76+
output_data = []
77+
78+
for result in results:
79+
80+
out = {}
81+
82+
out["timestamp"] = result["timestamp"]
83+
out["configuration"] = {
84+
k: v
85+
for k, v in result.items() if k in tune_params
86+
}
87+
88+
# collect configuration specific timings
89+
timings = dict()
90+
timings["compilation"] = result["compile_time"]
91+
timings["benchmark"] = result["benchmark_time"]
92+
timings["framework"] = result["framework_time"]
93+
timings["search_algorithm"] = result["strategy_time"]
94+
timings["validation"] = result["verification_time"]
95+
timings["runtimes"] = result["times"]
96+
out["times"] = timings
97+
98+
# encode the validity of the configuration
99+
out["invalidity"] = get_configuration_validity(result[objective])
100+
101+
# Kernel Tuner does not support producing results of configs that fail the correctness check
102+
# therefore correctness is always 1
103+
out["correctness"] = 1
104+
105+
# measurements gathers everything that was measured
106+
measurements = []
107+
for key, value in result.items():
108+
if key not in not_measurement_keys:
109+
measurements.append(
110+
dict(name=key,
111+
value=value,
112+
unit="ms" if key.startswith("time") else ""))
113+
out["measurements"] = measurements
114+
115+
# objectives
116+
# In Kernel Tuner we currently support only one objective at a time, this can be a user-defined
117+
# metric that combines scores from multiple different quantities into a single value to support
118+
# multi-objective tuning however.
119+
out["objectives"] = [objective]
120+
121+
# append to output
122+
output_data.append(out)
123+
124+
# write output_data to a JSON file
125+
version, _ = output_file_schema("results")
126+
output_json = dict(results=output_data, schema_version=version)
127+
with open(output_filename, 'w+') as fh:
128+
json.dump(output_json, fh)
129+
130+
131+
def get_dependencies(package='kernel_tuner'):
132+
""" Get the Python dependencies of Kernel Tuner currently installed and their version numbers """
133+
requirements = requires(package)
134+
deps = [Requirement(req).name for req in requirements]
135+
depends = []
136+
for dep in deps:
137+
try:
138+
depends.append(f"{dep}=={version(dep)}")
139+
except PackageNotFoundError:
140+
# uninstalled packages can not have been used to produce these results
141+
# so it is safe to ignore
142+
pass
143+
return depends
144+
145+
146+
def get_device_query(target):
147+
""" Get the information about GPUs in the current system, target is any of ['nvidia', 'amd'] """
148+
if target == "nvidia":
149+
nvidia_smi_out = subprocess.run(["nvidia-smi", "--query", "-x"],
150+
capture_output=True)
151+
nvidia_smi = xmltodict.parse(nvidia_smi_out.stdout)
152+
del nvidia_smi["nvidia_smi_log"]["gpu"]["processes"]
153+
return nvidia_smi
154+
elif target == "amd":
155+
rocm_smi_out = subprocess.run(["rocm-smi", "--showallinfo", "--json"],
156+
capture_output=True)
157+
return json.loads(rocm_smi_out.stdout)
158+
else:
159+
raise ValueError("get_device_query target not supported")
160+
161+
162+
def store_metadata_file(metadata_filename, target="nvidia"):
163+
""" Store the metadata about the current hardware and software environment in a JSON output file
164+
165+
This function produces a JSON file that adheres to the T4 auto-tuning metadata JSON schema.
166+
167+
:param metadata_filename: Name of the to be created metadata file
168+
:type metadata_filename: string
169+
170+
:param target: Target specifies whether to include the metadata of the 'nvidia' or 'amd' GPUs in the system
171+
:type target: string
172+
173+
"""
174+
if metadata_filename[-5:] != ".json":
175+
metadata_filename += ".json"
176+
metadata = {}
177+
178+
# lshw only works on Linux, this intentionally raises a FileNotFoundError when ran on systems that do not have it
179+
lshw_out = subprocess.run(["lshw", "-json"], capture_output=True)
180+
metadata["hardware"] = dict(lshw=json.loads(lshw_out.stdout))
181+
182+
# only works if nvidia-smi (for NVIDIA) or rocm-smi (for AMD) is present, raises FileNotFoundError when not present
183+
device_query = get_device_query(target)
184+
185+
metadata["environment"] = dict(device_query=device_query,
186+
requirements=get_dependencies())
187+
188+
# write metadata to JSON file
189+
version, _ = output_file_schema("metadata")
190+
metadata_json = dict(metadata=metadata, schema_version=version)
191+
with open(metadata_filename, 'w+') as fh:
192+
json.dump(metadata_json, fh, indent=" ")

kernel_tuner/runners/sequential.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
""" The default runner for sequentially tuning the parameter space """
22
import logging
33
from collections import OrderedDict
4+
from datetime import datetime, timezone
45
from time import perf_counter
56

67
from kernel_tuner.core import DeviceInterface
@@ -107,6 +108,7 @@ def run(self, parameter_space, kernel_options, tuning_options):
107108
total_time = 1000 * (perf_counter() - self.start_time) - warmup_time
108109
params['strategy_time'] = self.last_strategy_time
109110
params['framework_time'] = max(total_time - (params['compile_time'] + params['verification_time'] + params['benchmark_time'] + params['strategy_time']), 0)
111+
params['timestamp'] = str(datetime.now(timezone.utc))
110112
self.start_time = perf_counter()
111113

112114
if result:
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://github.com/odgaard/TuningSchema/blob/T4/metadata-schema.json",
4+
"title": "Open Autotuning Metadata Schema",
5+
"type": "object",
6+
"properties": {
7+
"schema_version": {
8+
"description": "The version number of the schema in major.minor.patch format.",
9+
"type": "string",
10+
"pattern": "^[0-9]{1,}.[0-9]{1,}.[0-9]{1,}$",
11+
"example": "1.0.0"
12+
},
13+
"metadata": {
14+
"type": "object",
15+
"properties": {
16+
"zenodo": {
17+
"type": "object",
18+
"description": "The zenodo metadata used to publish the artifact"
19+
},
20+
"hardware": {
21+
"type": "object",
22+
"properties": {
23+
"lshw": {
24+
"type": "array",
25+
"description": "The output of lshw as JSON"
26+
}
27+
}
28+
},
29+
"environment": {
30+
"type": "object",
31+
"properties": {
32+
"device_query": {
33+
"type": "object",
34+
"description": "The output from tools such as nvidia-smi as JSON"
35+
},
36+
"requirements": {
37+
"type": "array",
38+
"description": "the python libraries used as a list of strings"
39+
}
40+
}
41+
}
42+
}
43+
}
44+
}
45+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://github.com/odgaard/TuningSchema/blob/T4/results-schema.json",
4+
"description": "Open Autotuning Results Schema",
5+
"type": "object",
6+
"properties": {
7+
"schema_version": {
8+
"description": "The version number of the schema in major.minor.patch format.",
9+
"type": "string",
10+
"pattern": "^[0-9]{1,}.[0-9]{1,}.[0-9]{1,}$",
11+
"example": "1.0.0"
12+
},
13+
"results": {
14+
"type": "array",
15+
"items": {
16+
"type": "object",
17+
"properties": {
18+
"timestamp": {
19+
"type": "string"
20+
},
21+
"configuration": {
22+
"type": "object"
23+
},
24+
"objectives": {
25+
"type": "array"
26+
},
27+
"times": {
28+
"type": "object",
29+
"properties": {
30+
"compilation_time": {
31+
"type": "number"
32+
},
33+
"runtimes": {
34+
"type": "array"
35+
},
36+
"framework": {
37+
"type": "number"
38+
},
39+
"search_algorithm": {
40+
"type": "number"
41+
},
42+
"validation": {
43+
"type": "number"
44+
}
45+
}
46+
},
47+
"invalidity": {
48+
"enum": ["timeout", "compile", "runtime", "correctness", "constraints", "correct"]
49+
},
50+
"correctness": {
51+
"type": "number"
52+
},
53+
"measurements": {
54+
"type": "array",
55+
"items": {
56+
"type": "object",
57+
"properties": {
58+
"name": {
59+
"type": "string"
60+
},
61+
"value": {
62+
"type": "number"
63+
},
64+
"unit": {
65+
"type": "string"
66+
}
67+
}
68+
}
69+
}
70+
},
71+
"required": ["configuration", "times", "invalidity", "correctness"]
72+
}
73+
}
74+
}
75+
}

kernel_tuner/util.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -783,9 +783,14 @@ def process_cache(cache, kernel_options, tuning_options, runner):
783783
if cached_data["kernel_name"] != kernel_options.kernel_name:
784784
raise ValueError("Cannot load cache which contains results for different kernel")
785785
if "problem_size" in cached_data and not callable(kernel_options.problem_size):
786+
# if problem_size is not iterable, compare directly
787+
if not hasattr(kernel_options.problem_size, "__iter__"):
788+
if cached_data["problem_size"] != kernel_options.problem_size:
789+
raise ValueError("Cannot load cache which contains results for different problem_size")
790+
# else (problem_size is iterable)
786791
# cache returns list, problem_size is likely a tuple. Therefore, the next check
787792
# checks the equality of all items in the list/tuples individually
788-
if not all([i == j for i, j in zip(cached_data["problem_size"], kernel_options.problem_size)]):
793+
elif not all([i == j for i, j in zip(cached_data["problem_size"], kernel_options.problem_size)]):
789794
raise ValueError("Cannot load cache which contains results for different problem_size")
790795
if cached_data["tune_params_keys"] != list(tuning_options.tune_params.keys()):
791796
raise ValueError("Cannot load cache which contains results obtained with different tunable parameters")

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def readme():
5050
'Topic :: System :: Distributed Computing',
5151
'Development Status :: 5 - Production/Stable',
5252
],
53-
install_requires=['numpy>=1.13.3,<1.24.0', 'scipy>=1.8.1', 'jsonschema', 'python-constraint'],
53+
install_requires=['numpy>=1.13.3,<1.24.0', 'scipy>=1.8.1', 'jsonschema', 'python-constraint', 'xmltodict'],
5454
extras_require={
5555
'doc': ['sphinx', 'sphinx_rtd_theme', 'nbsphinx', 'pytest', 'ipython', 'markupsafe==2.0.1'],
5656
'cuda': ['pycuda', 'nvidia-ml-py', 'pynvml>=11.4.1'],

0 commit comments

Comments
 (0)