Files
apache--tvm/python/tvm/s_tir/meta_schedule/post_optimization/space.py
T
wehub-resource-sync 26446540fa
Lint / lint (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Windows (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:36:25 +08:00

261 lines
9.0 KiB
Python

# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""The class of Space used to optimize the Meta parameters"""
import json
import random
from copy import deepcopy
from typing import Any
import numpy as np # type: ignore
from tvm.s_tir import Schedule
from tvm.s_tir import meta_schedule as ms
from tvm.s_tir.meta_schedule.database import TuningRecord, Workload
from tvm.s_tir.meta_schedule.utils import remove_build_dir
from tvm.target import Target
from .utils import write_file
class Space:
"""Space class
Parameters
----------
data: json data
A json file template
workload: json data
A json file workload
target: Target data
Target device information
"""
def __init__(self, data: Any, workload: Any, target: Target):
self.cfg = deepcopy(data)
self._id = data[0]
self.workload = Workload.from_json(workload)
self.target = target
self.dev = self.get_device_type(target)
self.total_dims = 0
self.dims: list[int] = []
self.start: list[int] = []
self.config_space: dict[str, list[int]] = dict()
self.create_space()
def __repr__(self) -> str:
"""Print the config space"""
out = ""
for key in self.config_space:
out += f"{key}: dims={self.config_space[key]}\n"
out += f"Total dimensions: {self.total_dims}\n"
return out
def __str__(self) -> str:
"""Print the config space"""
out = ""
for key in self.config_space:
out += f"{key}: dims={self.config_space[key]}\n"
out += f"Total dimensions: {self.total_dims}\n"
return out
def get_value(self, key, pos):
"""Return the space"""
return self.config_space[key][pos]
def add_space(self, space_list: list, element_list: list, limit=10000) -> list[int]:
"""Return a list without repeat and with limited value"""
new_list = element_list
for elem in space_list:
if elem not in new_list and elem <= limit:
new_list.append(elem)
return new_list
def knob2point(self, knob):
"""Convert a array to point"""
point = 0
for j, k in enumerate(knob):
point += int(np.prod(self.dims[:j])) * k
return point
def point2knob(self, point):
"""Convert point form (single integer) to knob (vector)"""
knob = []
for dim in self.dims:
knob.append(point % dim)
point //= dim
return knob
def power_of_two(self, min_value: int, max_value: int) -> list:
"""Return power of two array in interval"""
return [1 << i for i in range(min_value, max_value + 1)]
def get_index(self, array: list, value: int):
"""returns an index if it finds the value"""
for i in range(len(array)):
if array[i][0] == value:
return i
return -1
def template(self, values=None, create=True):
"""Generate the template from the values"""
idx = -1
config = deepcopy(self.cfg[1])
for counter, cfg in enumerate(config[0][0]):
opt = cfg[0]
if opt == "Annotate":
ann_key = cfg[2]
if ann_key == ["meta_schedule.parallel"]:
interval = self.power_of_two(5, 9)
elif ann_key == ["meta_schedule.vectorize"]:
interval = self.power_of_two(4, 8)
elif ann_key == ["pragma_auto_unroll_max_step"]:
interval = self.power_of_two(7, 11)
elif ann_key == ["meta_schedule.thread_extent_low_inclusive"]:
interval = self.power_of_two(5, 6)
elif ann_key == ["meta_schedule.thread_extent_high_inclusive"]:
interval = self.power_of_two(8, 12)
else:
continue
idx += 1
key = f"ann_{idx}"
ann_value = cfg[1][1]
if create:
self.config_space[key] = self.add_space(interval, [ann_value])
else:
cfg[1][1] = self.get_value(key, values[idx])
elif opt == "SamplePerfectTile":
tile = config[0][1]
tile_idx = self.get_index(tile, counter)
tile_val = tile[tile_idx][1]
interval = self.power_of_two(1, 6)
for i in range(len(tile_val)):
idx += 1
key = f"sp_{counter}_{idx}"
split = tile_val[i]
if create:
self.config_space[key] = self.add_space(interval, [split])
else:
config[0][1][tile_idx][1][i] = self.get_value(key, values[idx])
elif opt == "TransformLayout":
del config[0][0][counter]
if create:
return None
return config
def create_space(self):
"""Create the space using Meta's space"""
self.template(create=True)
# print(self.config_space)
self.dims = []
for key in self.config_space:
self.dims.append(len(self.config_space[key]))
self.total_dims = 1
if len(self.dims) > 0:
for dim in self.dims:
self.total_dims *= dim
def get_device_type(self, target: Target) -> str:
"""Get the device type string from a target.
Parameters
----------
target : Target
The target to get the device type from.
Returns
-------
device_type : str
The device type string.
"""
if target.kind.name == "llvm":
return "cpu"
elif target.kind.name == "cuda":
return "cuda"
else:
raise RuntimeError(f"Unsupported target kind for device type: {target.kind.name}")
def save_log(
self,
path: str,
record: ms.database.TuningRecord,
results: ms.runner.RunnerResult,
) -> None:
"""Save the log file"""
new_json = [self._id, record.as_json()]
new_json[1][1] = results
write_file([new_json], path, "a")
def run(
self,
json_file_list,
final_log,
timeout=10,
number=2,
repeat=3,
min_repeat_ms=0,
cpu_cache=False,
):
"""Execute a log file and save"""
builder = ms.builder.LocalBuilder(timeout_sec=timeout)
runner = ms.runner.LocalRunner(
evaluator_config=ms.runner.EvaluatorConfig(
number=number,
repeat=repeat,
min_repeat_ms=min_repeat_ms,
enable_cpu_cache_flush=cpu_cache,
),
)
results = np.full(len(json_file_list), [10000], dtype=list)
records, mods = [], []
for i, cfg in enumerate(json_file_list):
try:
record = TuningRecord.from_json(json.loads(json.dumps(cfg)), self.workload)
sch = Schedule(self.workload.mod)
# In some layers this is a heavy impact in time cost, so
# I applied this only 25% of the samples.
remove_postproc = random.random() > 0.75
record.trace.apply_to_schedule(sch, remove_postproc=remove_postproc)
mods.append(sch.mod)
records.append(record)
except Exception: # pylint: disable=broad-except, invalid-name
continue
builder_res = builder.build([ms.builder.BuilderInput(mod, self.target) for mod in mods])
for i, record in enumerate(records):
try:
inp = ms.runner.RunnerInput(
builder_res[i].artifact_path,
device_type=self.dev,
args_info=ms.arg_info.TensorInfo.from_prim_func(mods[i]["main"]),
)
runner_res = runner.run([inp])[0].result()
results[i] = [v.value for v in runner_res.run_secs] # type: ignore
except Exception: # pylint: disable=broad-except, invalid-name
results[i] = [1e10]
continue
# save the solution in json file
self.save_log(final_log, record, results[i])
# clean up
remove_build_dir(builder_res[i].artifact_path)
return results