chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
# 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.
|
||||
"""Testing utilities in meta schedule"""
|
||||
|
||||
# NOTE: Do not import any module here by default
|
||||
@@ -0,0 +1,54 @@
|
||||
# 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.
|
||||
"""Customized builder and runner methods"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
import numpy as np # type: ignore
|
||||
|
||||
from tvm.runtime import Executable, Module
|
||||
from tvm.s_tir.meta_schedule.runner import RPCConfig
|
||||
|
||||
|
||||
def run_module_via_rpc(
|
||||
rpc_config: RPCConfig,
|
||||
lib: Module | Executable,
|
||||
dev_type: str,
|
||||
args: dict[int, np.ndarray] | dict[str, np.ndarray],
|
||||
continuation: Callable,
|
||||
):
|
||||
"""Execute a tvm.runtime.Module on RPC remote"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from tvm.runtime import ndarray
|
||||
from tvm.support.tar import tar
|
||||
|
||||
# pylint: enable=import-outside-toplevel
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
filename = os.path.join(tmp_dir, "tvm_tmp_mod." + tar.output_format)
|
||||
lib.export_library(filename, fcompile=tar)
|
||||
session = rpc_config.connect_server()
|
||||
session.upload(filename)
|
||||
_, filename = os.path.split(filename)
|
||||
rt_mod = session.load_module(filename)
|
||||
dev = session.device(dev_type, 0)
|
||||
nd_args = {k: ndarray.array(v, dev) for k, v in args.items()}
|
||||
return continuation(rt_mod, dev, nd_args)
|
||||
@@ -0,0 +1,199 @@
|
||||
# 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.
|
||||
# pylint: disable=missing-docstring
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
|
||||
from tqdm import tqdm # type: ignore
|
||||
|
||||
from tvm.s_tir import meta_schedule as ms
|
||||
from tvm.target import Target
|
||||
|
||||
|
||||
def _parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--candidate_cache_dir", type=str, help="Please provide the full path to the candidates."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--result_cache_dir", type=str, help="Please provide the full path to the result database."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--target",
|
||||
type=str,
|
||||
default="nvidia/nvidia-v100",
|
||||
help="Please specify the target hardware for tuning context.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rpc_host", type=str, help="Please provide the private IPv4 address for the tracker."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rpc_port", type=int, default=4445, help="Please provide the port for the tracker."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rpc_key",
|
||||
type=str,
|
||||
default="p3.2xlarge",
|
||||
help="Please provide the key for the rpc servers.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--builder_timeout_sec",
|
||||
type=int,
|
||||
default=10,
|
||||
help="The time for the builder session to time out.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--min_repeat_ms", type=int, default=100, help="The time for preheating the gpu."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--runner_timeout_sec",
|
||||
type=int,
|
||||
default=100,
|
||||
help="The time for the runner session to time out.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cpu_flush", type=bool, default=False, help="Whether to enable cpu cache flush or not."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--batch_size",
|
||||
type=int,
|
||||
default=128,
|
||||
help="The batch size of candidates sent to builder and runner each time.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
# pylint: disable=too-many-locals
|
||||
def measure_candidates(database, builder, runner):
|
||||
"""Send the candidates to builder and runner for distributed measurement,
|
||||
and save the results in a new json database.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
database : JSONDatabase
|
||||
The database for candidates to be measured.
|
||||
builder : Builder
|
||||
The builder for building the candidates.
|
||||
runner : Runner
|
||||
The runner for measuring the candidates.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
candidates, runner_results, build_fail_indices, run_fail_indices = [], [], [], []
|
||||
context = ms.TuneContext(target=Target(args.target))
|
||||
tuning_records = database.get_all_tuning_records()
|
||||
for record in tuning_records:
|
||||
candidates.append(record.as_measure_candidate())
|
||||
with ms.Profiler() as profiler:
|
||||
for idx in range(0, len(candidates), args.batch_size):
|
||||
batch_candidates = candidates[idx : idx + args.batch_size]
|
||||
context._set_measure_candidates(batch_candidates) # pylint: disable=protected-access
|
||||
with ms.Profiler.timeit("build"):
|
||||
context._send_to_builder(builder) # pylint: disable=protected-access
|
||||
with ms.Profiler.timeit("run"):
|
||||
context._send_to_runner(runner) # pylint: disable=protected-access
|
||||
batch_runner_results = context._join() # pylint: disable=protected-access
|
||||
runner_results.extend(batch_runner_results)
|
||||
for i, result in enumerate(context.builder_results):
|
||||
if result.error_msg is None:
|
||||
ms.utils.remove_build_dir(result.artifact_path)
|
||||
else:
|
||||
build_fail_indices.append(i + idx)
|
||||
context._clear_measure_state() # pylint: disable=protected-access
|
||||
|
||||
model_name, workload_name = database.path_workload.split("/")[-2:]
|
||||
record_name = database.path_tuning_record.split("/")[-1]
|
||||
new_database = ms.database.JSONDatabase(
|
||||
path_workload=os.path.join(args.result_cache_dir, model_name, workload_name),
|
||||
path_tuning_record=os.path.join(args.result_cache_dir, model_name, record_name),
|
||||
)
|
||||
workload = tuning_records[0].workload
|
||||
new_database.commit_workload(workload.mod)
|
||||
for i, (record, result) in enumerate(zip(tuning_records, runner_results)):
|
||||
if result.error_msg is None:
|
||||
new_database.commit_tuning_record(
|
||||
ms.database.TuningRecord(
|
||||
trace=record.trace,
|
||||
workload=workload,
|
||||
run_secs=[v.value for v in result.run_secs],
|
||||
target=Target(args.target),
|
||||
)
|
||||
)
|
||||
else:
|
||||
run_fail_indices.append(i)
|
||||
fail_indices_name = workload_name.replace("_workload.json", "_failed_indices.txt")
|
||||
with open(
|
||||
os.path.join(args.result_cache_dir, model_name, fail_indices_name), "w", encoding="utf8"
|
||||
) as file:
|
||||
file.write(" ".join([str(n) for n in run_fail_indices]))
|
||||
print(
|
||||
f"Builder time: {profiler.get()['build']}, Runner time: {profiler.get()['run']}\n\
|
||||
Failed number of builds: {len(build_fail_indices)},\
|
||||
Failed number of runs: {len(run_fail_indices)}"
|
||||
)
|
||||
|
||||
|
||||
args = _parse_args() # pylint: disable=invalid-name
|
||||
|
||||
|
||||
def main():
|
||||
builder = ms.builder.LocalBuilder(timeout_sec=args.builder_timeout_sec)
|
||||
runner = ms.runner.RPCRunner(
|
||||
rpc_config=ms.runner.RPCConfig(
|
||||
tracker_host=args.rpc_host,
|
||||
tracker_port=args.rpc_port,
|
||||
tracker_key=args.rpc_key,
|
||||
session_timeout_sec=args.runner_timeout_sec,
|
||||
),
|
||||
evaluator_config=ms.runner.EvaluatorConfig(
|
||||
number=3,
|
||||
repeat=1,
|
||||
min_repeat_ms=args.min_repeat_ms,
|
||||
enable_cpu_cache_flush=args.cpu_flush,
|
||||
),
|
||||
max_workers=os.cpu_count(),
|
||||
)
|
||||
if not os.path.isdir(args.candidate_cache_dir):
|
||||
raise Exception("Please provide a correct candidate cache dir.")
|
||||
try:
|
||||
os.makedirs(args.result_cache_dir, exist_ok=True)
|
||||
except OSError:
|
||||
print(f"Directory {args.result_cache_dir} cannot be created successfully.")
|
||||
model_dirs = glob.glob(os.path.join(args.candidate_cache_dir, "*"))
|
||||
for model_dir in model_dirs:
|
||||
model_name = model_dir.split("/")[-1]
|
||||
os.makedirs(os.path.join(args.result_cache_dir, model_name), exist_ok=True)
|
||||
all_tasks = glob.glob(os.path.join(model_dir, "*.json"))
|
||||
workload_paths = []
|
||||
for path in all_tasks:
|
||||
if path.endswith("_workload.json"):
|
||||
workload_paths.append(path)
|
||||
for workload_path in tqdm(workload_paths):
|
||||
candidate_path = workload_path.replace("_workload.json", "_candidates.json")
|
||||
database = ms.database.JSONDatabase(
|
||||
path_workload=workload_path,
|
||||
path_tuning_record=candidate_path,
|
||||
)
|
||||
measure_candidates(database, builder, runner)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,63 @@
|
||||
# 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.
|
||||
"""Dummy objects for testing."""
|
||||
|
||||
import random
|
||||
|
||||
from tvm.ir.utils import derived_object
|
||||
from tvm.s_tir.schedule import Trace
|
||||
|
||||
from ..builder import BuilderInput, BuilderResult, PyBuilder
|
||||
from ..mutator import PyMutator
|
||||
from ..runner import PyRunner, PyRunnerFuture, RunnerFuture, RunnerInput, RunnerResult
|
||||
from ..tune_context import TuneContext # pylint: disable=unused-import
|
||||
|
||||
|
||||
@derived_object
|
||||
class DummyRunnerFuture(PyRunnerFuture):
|
||||
def done(self) -> bool:
|
||||
return True
|
||||
|
||||
def result(self) -> RunnerResult:
|
||||
run_secs = [random.uniform(5, 30) for _ in range(random.randint(1, 10))]
|
||||
return RunnerResult(run_secs, None)
|
||||
|
||||
|
||||
@derived_object
|
||||
class DummyBuilder(PyBuilder):
|
||||
def build(self, build_inputs: list[BuilderInput]) -> list[BuilderResult]:
|
||||
return [BuilderResult("test_path", None) for _ in build_inputs]
|
||||
|
||||
|
||||
@derived_object
|
||||
class DummyRunner(PyRunner):
|
||||
def run(self, runner_inputs: list[RunnerInput]) -> list[RunnerFuture]:
|
||||
return [DummyRunnerFuture() for _ in runner_inputs] # type: ignore
|
||||
|
||||
|
||||
@derived_object
|
||||
class DummyMutator(PyMutator):
|
||||
"""Dummy Mutator for testing"""
|
||||
|
||||
def _initialize_with_tune_context(self, context: "TuneContext") -> None:
|
||||
pass
|
||||
|
||||
def apply(self, trace: Trace, _) -> Trace | None:
|
||||
return Trace(trace.insts, {})
|
||||
|
||||
def clone(self):
|
||||
return DummyMutator()
|
||||
@@ -0,0 +1,72 @@
|
||||
# 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.
|
||||
"""RPC tracker and server running locally"""
|
||||
|
||||
from tvm.rpc.server import Server
|
||||
from tvm.rpc.tracker import Tracker
|
||||
|
||||
|
||||
class LocalRPC:
|
||||
"""A pair of RPC tracker/server running locally
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tracker_host : str
|
||||
The host URL of the tracker
|
||||
tracker_port : int
|
||||
The port of the tracker
|
||||
tracker_key: str
|
||||
The key used in the tracker to refer to a worker
|
||||
"""
|
||||
|
||||
tracker_host: str
|
||||
tracker_port: int
|
||||
tracker_key: str
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tracker_key: str = "key",
|
||||
silent: bool = False,
|
||||
no_fork: bool = False,
|
||||
) -> None:
|
||||
self.tracker = Tracker(
|
||||
silent=silent,
|
||||
port=9190,
|
||||
port_end=12345,
|
||||
)
|
||||
self.server = Server(
|
||||
host="0.0.0.0",
|
||||
is_proxy=False,
|
||||
tracker_addr=(self.tracker.host, self.tracker.port),
|
||||
key=tracker_key,
|
||||
silent=silent,
|
||||
no_fork=no_fork,
|
||||
port=9190,
|
||||
port_end=12345,
|
||||
)
|
||||
self.tracker_host = self.tracker.host
|
||||
self.tracker_port = self.tracker.port
|
||||
self.tracker_key = tracker_key
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, _type, _value, _traceback):
|
||||
if hasattr(self, "server"):
|
||||
del self.server
|
||||
if hasattr(self, "tracker"):
|
||||
del self.tracker
|
||||
@@ -0,0 +1,150 @@
|
||||
# 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.
|
||||
# pylint: disable=missing-module-docstring,missing-function-docstring,missing-class-docstring
|
||||
|
||||
# isort: off
|
||||
from typing import Literal
|
||||
|
||||
# isort: on
|
||||
|
||||
import tvm_ffi
|
||||
|
||||
from tvm.ir import IRModule
|
||||
from tvm.s_tir import Schedule
|
||||
from tvm.s_tir import meta_schedule as ms
|
||||
from tvm.s_tir.schedule import Trace
|
||||
from tvm.s_tir.schedule.testing import verify_trace_roundtrip
|
||||
from tvm.target import Target
|
||||
|
||||
|
||||
def get_rules(
|
||||
kind: Literal["llvm", "cuda", "cuda-tensorcore", "hexagon"],
|
||||
types: type | tuple[type, ...],
|
||||
) -> list[ms.ScheduleRule]:
|
||||
"""Get default schedule rules"""
|
||||
rules = ms.ScheduleRule.create(kind)
|
||||
return [rule for rule in rules if isinstance(rule, types)]
|
||||
|
||||
|
||||
def structural_equal_no_gs(mod1: IRModule, mod2: IRModule) -> bool:
|
||||
"""
|
||||
Checks structural equality but ignores global symbols
|
||||
"""
|
||||
|
||||
# for every function in the modules, remove global symbols from the attrs and then compare
|
||||
def remove_global_symbols(mod: IRModule) -> IRModule:
|
||||
stripped_mod = IRModule()
|
||||
for global_var in mod.get_global_vars():
|
||||
func = mod[global_var]
|
||||
stripped_mod[global_var] = func.without_attr("global_symbol")
|
||||
return stripped_mod
|
||||
|
||||
return tvm_ffi.structural_equal(remove_global_symbols(mod1), remove_global_symbols(mod2))
|
||||
|
||||
|
||||
def generate_design_space(
|
||||
kind: Literal["llvm", "cuda", "cuda-tensorcore", "hexagon"],
|
||||
mod: IRModule,
|
||||
target: Target,
|
||||
types: type | tuple[type, ...],
|
||||
sch_rules: list[ms.ScheduleRule] | None = None,
|
||||
) -> list[Schedule]:
|
||||
if sch_rules is None:
|
||||
sch_rules = get_rules(kind, types)
|
||||
else:
|
||||
assert types is None
|
||||
return ms.TuneContext(
|
||||
mod=mod,
|
||||
target=target,
|
||||
space_generator=ms.space_generator.PostOrderApply(
|
||||
sch_rules=sch_rules,
|
||||
postprocs=[],
|
||||
mutator_probs={},
|
||||
),
|
||||
task_name="test",
|
||||
).generate_design_space()
|
||||
|
||||
|
||||
def _find_match_sketch_id(
|
||||
mod: IRModule,
|
||||
sketches: list[Schedule],
|
||||
expected_mod: IRModule,
|
||||
expected_decision: list[tuple[str, list[int]]],
|
||||
*,
|
||||
debug_mask="all",
|
||||
) -> int | None:
|
||||
for sketch_id, sketch in enumerate(sketches):
|
||||
i = 0
|
||||
new_decisions = {}
|
||||
for inst in sketch.trace.insts:
|
||||
if not inst.kind.name.startswith("Sample"):
|
||||
continue
|
||||
assert i < len(expected_decision)
|
||||
if inst.kind.name == expected_decision[i][0]:
|
||||
new_decisions[inst] = expected_decision[i][1]
|
||||
i += 1
|
||||
if len(new_decisions) != len(expected_decision):
|
||||
continue
|
||||
sch = Schedule(mod, debug_mask=debug_mask)
|
||||
Trace(
|
||||
insts=sketch.trace.insts,
|
||||
decisions=new_decisions,
|
||||
).apply_to_schedule(sch, remove_postproc=True)
|
||||
if structural_equal_no_gs(sch.mod, expected_mod):
|
||||
verify_trace_roundtrip(sch=sch, mod=mod, debug_mask=debug_mask, text_format="json")
|
||||
return sketch_id
|
||||
return None
|
||||
|
||||
|
||||
def check_sketches(
|
||||
mod: IRModule,
|
||||
sketches: list[Schedule],
|
||||
expected_mods: list[IRModule],
|
||||
expected_decisions: list[list[tuple[str, list[int]]]],
|
||||
*,
|
||||
debug_mask="all",
|
||||
):
|
||||
assert len(expected_mods) == len(expected_decisions)
|
||||
assert len(sketches) == len(expected_mods)
|
||||
expected_mods = [
|
||||
IRModule({"main": m}) if not isinstance(m, IRModule) else m for m in expected_mods
|
||||
]
|
||||
sketches = list(sketches)
|
||||
for expected_id, (expected_mod, expected_decision) in enumerate(
|
||||
zip(expected_mods, expected_decisions)
|
||||
):
|
||||
sketch_id = _find_match_sketch_id(
|
||||
mod,
|
||||
sketches,
|
||||
expected_mod,
|
||||
expected_decision,
|
||||
debug_mask=debug_mask,
|
||||
)
|
||||
if sketch_id is None:
|
||||
raise AssertionError(
|
||||
f"Expected sketch #{expected_id} doesn't exist in the generated sketches."
|
||||
)
|
||||
sketches.pop(sketch_id)
|
||||
|
||||
|
||||
def print_sketches(sketches: list[Schedule]):
|
||||
for i, sch in enumerate(sketches):
|
||||
print(f"###### {i}")
|
||||
sch.mod.show(black_format=False)
|
||||
for inst in sch.trace.insts:
|
||||
if inst in sch.trace.decisions:
|
||||
print(f'("{inst.kind.name}", {sch.trace.decisions[inst]}),')
|
||||
@@ -0,0 +1,837 @@
|
||||
# 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.
|
||||
# ruff: noqa: E741
|
||||
"""Workloads in TE"""
|
||||
|
||||
# pylint: disable=missing-docstring
|
||||
|
||||
from tvm import te, tirx, topi
|
||||
from tvm.target import Target
|
||||
|
||||
|
||||
def batch_matmul_nkkm( # pylint: disable=invalid-name,missing-docstring
|
||||
B: int,
|
||||
N: int,
|
||||
M: int,
|
||||
K: int,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
x = te.placeholder((B, N, K), name="X", dtype=in_dtype)
|
||||
y = te.placeholder((B, K, M), name="Y", dtype=in_dtype)
|
||||
k = te.reduce_axis((0, K), name="k")
|
||||
z = te.compute( # pylint: disable=invalid-name
|
||||
(B, N, M),
|
||||
lambda b, i, j: te.sum(
|
||||
x[b][i][k].astype(out_dtype) * y[b][k][j].astype(out_dtype),
|
||||
axis=[k],
|
||||
),
|
||||
name="Z",
|
||||
)
|
||||
return (x, y, z)
|
||||
|
||||
|
||||
def conv1d_nlc( # pylint: disable=invalid-name,missing-docstring
|
||||
N: int,
|
||||
L: int,
|
||||
CI: int,
|
||||
CO: int,
|
||||
kernel_size: int,
|
||||
stride: int = 1,
|
||||
padding: int = 0,
|
||||
dilation: int = 1,
|
||||
groups: int = 1,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
inputs = te.placeholder((N, L, CI), name="inputs", dtype=in_dtype)
|
||||
weight = te.placeholder((kernel_size, CI // groups, CO), name="weight", dtype=in_dtype)
|
||||
|
||||
batch_size, in_len, _ = inputs.shape
|
||||
k_len, channel_per_group, out_channel = weight.shape
|
||||
out_channel_per_group = out_channel // groups
|
||||
out_len = (in_len + 2 * padding - dilation * (k_len - 1) - 1) // stride + 1
|
||||
rc = te.reduce_axis((0, channel_per_group), name="rc")
|
||||
rl = te.reduce_axis((0, k_len), name="rl")
|
||||
|
||||
padded = topi.nn.pad(inputs, [0, padding, 0])
|
||||
output = te.compute(
|
||||
(batch_size, out_len, out_channel),
|
||||
lambda n, l, co: te.sum(
|
||||
(
|
||||
padded[
|
||||
n,
|
||||
l * stride + rl * dilation,
|
||||
co // out_channel_per_group * channel_per_group + rc,
|
||||
].astype(out_dtype)
|
||||
* weight[rl, rc, co].astype(out_dtype)
|
||||
),
|
||||
axis=[rl, rc],
|
||||
),
|
||||
name="conv1d_nlc",
|
||||
)
|
||||
return (inputs, weight, output)
|
||||
|
||||
|
||||
def conv2d_nhwc( # pylint: disable=invalid-name,missing-docstring
|
||||
N: int,
|
||||
H: int,
|
||||
W: int,
|
||||
CI: int,
|
||||
CO: int,
|
||||
kernel_size: int,
|
||||
stride: int = 1,
|
||||
padding: int = 0,
|
||||
dilation: int = 1,
|
||||
groups: int = 1,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
inputs = te.placeholder((N, H, W, CI), name="inputs", dtype=in_dtype)
|
||||
weight = te.placeholder(
|
||||
(kernel_size, kernel_size, CI // groups, CO), name="weight", dtype=in_dtype
|
||||
)
|
||||
batch_size, in_h, in_w, _ = inputs.shape
|
||||
k_h, k_w, channel_per_group, out_channel = weight.shape
|
||||
out_channel_per_group = out_channel // groups
|
||||
|
||||
out_h = (in_h + 2 * padding - dilation * (k_h - 1) - 1) // stride + 1
|
||||
out_w = (in_w + 2 * padding - dilation * (k_w - 1) - 1) // stride + 1
|
||||
rh = te.reduce_axis((0, k_h), name="rh")
|
||||
rw = te.reduce_axis((0, k_w), name="rw")
|
||||
rc = te.reduce_axis((0, channel_per_group), name="rc")
|
||||
|
||||
padded = topi.nn.pad(inputs, [0, padding, padding, 0])
|
||||
output = te.compute(
|
||||
(batch_size, out_h, out_w, out_channel),
|
||||
lambda n, h, w, co: te.sum(
|
||||
(
|
||||
padded[
|
||||
n,
|
||||
h * stride + rh * dilation,
|
||||
w * stride + rw * dilation,
|
||||
co // out_channel_per_group * channel_per_group + rc,
|
||||
].astype(out_dtype)
|
||||
* weight[rh, rw, rc, co].astype(out_dtype)
|
||||
),
|
||||
axis=[rh, rw, rc],
|
||||
),
|
||||
name="conv2d_nhwc",
|
||||
)
|
||||
return (inputs, weight, output)
|
||||
|
||||
|
||||
def conv3d_ndhwc( # pylint: disable=invalid-name,missing-docstring
|
||||
N: int,
|
||||
D: int,
|
||||
H: int,
|
||||
W: int,
|
||||
CI: int,
|
||||
CO: int,
|
||||
kernel_size: int,
|
||||
stride: int = 1,
|
||||
padding: int = 0,
|
||||
dilation: int = 1,
|
||||
groups: int = 1,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
inputs = te.placeholder((N, D, H, W, CI), name="inputs", dtype=in_dtype)
|
||||
weight = te.placeholder(
|
||||
(kernel_size, kernel_size, kernel_size, CI // groups, CO), name="weight", dtype=in_dtype
|
||||
)
|
||||
batch_size, in_d, in_h, in_w, _ = inputs.shape
|
||||
k_d, k_h, k_w, channel_per_group, out_channel = weight.shape
|
||||
out_channel_per_group = out_channel // groups
|
||||
|
||||
out_d = (in_d + 2 * padding - dilation * (k_d - 1) - 1) // stride + 1
|
||||
out_h = (in_h + 2 * padding - dilation * (k_h - 1) - 1) // stride + 1
|
||||
out_w = (in_w + 2 * padding - dilation * (k_w - 1) - 1) // stride + 1
|
||||
rd = te.reduce_axis((0, k_d), name="rd")
|
||||
rh = te.reduce_axis((0, k_h), name="rh")
|
||||
rw = te.reduce_axis((0, k_w), name="rw")
|
||||
rc = te.reduce_axis((0, channel_per_group), name="rc")
|
||||
|
||||
padded = topi.nn.pad(inputs, [0, padding, padding, padding, 0])
|
||||
output = te.compute(
|
||||
(batch_size, out_d, out_h, out_w, out_channel),
|
||||
lambda n, d, h, w, co: te.sum(
|
||||
(
|
||||
padded[
|
||||
n,
|
||||
d * stride + rd * dilation,
|
||||
h * stride + rh * dilation,
|
||||
w * stride + rw * dilation,
|
||||
co // out_channel_per_group * channel_per_group + rc,
|
||||
].astype(out_dtype)
|
||||
* weight[rd, rh, rw, rc, co].astype(out_dtype)
|
||||
),
|
||||
axis=[rd, rh, rw, rc],
|
||||
),
|
||||
name="conv3d_ndhwc",
|
||||
)
|
||||
return (inputs, weight, output)
|
||||
|
||||
|
||||
def depthwise_conv2d_nhwc( # pylint: disable=invalid-name,missing-docstring
|
||||
N: int,
|
||||
H: int,
|
||||
W: int,
|
||||
C: int,
|
||||
kernel_size: int,
|
||||
stride: int = 1,
|
||||
padding: int = 0,
|
||||
dilation: int = 1,
|
||||
factor: int = 1,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
inputs = te.placeholder((N, H, W, C), dtype=in_dtype)
|
||||
weight = te.placeholder((factor, kernel_size, kernel_size, C), dtype=in_dtype)
|
||||
batch_size, in_h, in_w, in_channel = inputs.shape
|
||||
factor, k_h, k_w, in_channel = weight.shape
|
||||
out_channel = in_channel * factor
|
||||
assert int(factor) == 1, "Not optimized for factor != 1"
|
||||
out_h = (in_h + 2 * padding - dilation * (k_h - 1) - 1) // stride + 1
|
||||
out_w = (in_w + 2 * padding - dilation * (k_w - 1) - 1) // stride + 1
|
||||
rh = te.reduce_axis((0, k_h), name="rh")
|
||||
rw = te.reduce_axis((0, k_w), name="rw")
|
||||
padded = topi.nn.pad(inputs, [0, padding, padding, 0])
|
||||
output = te.compute(
|
||||
(batch_size, out_h, out_w, out_channel),
|
||||
lambda n, h, w, c: te.sum(
|
||||
(
|
||||
padded[
|
||||
n,
|
||||
h * stride + rh * dilation,
|
||||
w * stride + rw * dilation,
|
||||
c // factor,
|
||||
].astype(out_dtype)
|
||||
* weight[c % factor, rh, rw, c // factor].astype(out_dtype)
|
||||
),
|
||||
axis=[rh, rw],
|
||||
),
|
||||
name="depth_conv2d_nhwc",
|
||||
)
|
||||
return (inputs, weight, output)
|
||||
|
||||
|
||||
def conv2d_transpose_nhwc( # pylint: disable=invalid-name,missing-docstring
|
||||
N: int,
|
||||
H: int,
|
||||
W: int,
|
||||
CI: int,
|
||||
CO: int,
|
||||
kernel_size: int,
|
||||
stride: int = 1,
|
||||
padding: int = 0,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
inputs = te.placeholder((N, H, W, CI), name="inputs", dtype=in_dtype)
|
||||
weight = te.placeholder((kernel_size, kernel_size, CI, CO), name="weight", dtype=in_dtype)
|
||||
|
||||
batch, in_h, in_w, in_c = inputs.shape
|
||||
filter_h, filter_w, in_c, out_c = weight.shape
|
||||
stride_h, stride_w = (stride, stride)
|
||||
|
||||
# compute padding
|
||||
fpad_top, fpad_left, fpad_bottom, fpad_right = topi.nn.get_pad_tuple(
|
||||
padding, (filter_h, filter_w)
|
||||
)
|
||||
bpad_top = filter_h - 1 - fpad_top
|
||||
bpad_bottom = filter_h - 1 - fpad_bottom
|
||||
bpad_left = filter_w - 1 - fpad_left
|
||||
bpad_right = filter_w - 1 - fpad_right
|
||||
|
||||
# padding stage
|
||||
padded = topi.nn.pad(
|
||||
inputs,
|
||||
[
|
||||
0,
|
||||
(bpad_top + stride_h - 1) // stride_h,
|
||||
(bpad_left + stride_w - 1) // stride_w,
|
||||
0,
|
||||
],
|
||||
[
|
||||
0,
|
||||
(bpad_bottom + stride_h - 1) // stride_h,
|
||||
(bpad_right + stride_w - 1) // stride_w,
|
||||
0,
|
||||
],
|
||||
)
|
||||
|
||||
# remove extra padding introduced by dilatation
|
||||
idx_div = te.indexdiv
|
||||
idx_mod = te.indexmod
|
||||
border_h = idx_mod(stride_h - idx_mod(bpad_top, stride_h), stride_h)
|
||||
border_w = idx_mod(stride_w - idx_mod(bpad_left, stride_w), stride_w)
|
||||
|
||||
# dilation stage
|
||||
strides = [1, stride_h, stride_w, 1]
|
||||
n = len(padded.shape)
|
||||
|
||||
# We should embed this dilation directly into te.compute rather than creating a new te.compute.
|
||||
# Only in this way can we use unroll to eliminate the multiplication of zeros.
|
||||
def _dilate(*indices):
|
||||
not_zero = []
|
||||
index_tuple = []
|
||||
for i in range(n):
|
||||
if not strides[i] == 1:
|
||||
index_tuple.append(idx_div(indices[i], strides[i]))
|
||||
not_zero.append(idx_mod(indices[i], strides[i]).equal(0))
|
||||
else:
|
||||
index_tuple.append(indices[i])
|
||||
if not_zero:
|
||||
not_zero = te.all(*not_zero)
|
||||
return te.if_then_else(not_zero, padded(*index_tuple), tirx.const(0.0, padded.dtype))
|
||||
return padded(*index_tuple)
|
||||
|
||||
# convolution stage
|
||||
out_h = (in_h - 1) * stride_h - fpad_top - fpad_bottom + filter_h
|
||||
out_w = (in_w - 1) * stride_w - fpad_left - fpad_right + filter_w
|
||||
rc = te.reduce_axis((0, in_c), name="rc")
|
||||
rh = te.reduce_axis((0, filter_h), name="rh")
|
||||
rw = te.reduce_axis((0, filter_w), name="rw")
|
||||
|
||||
output = te.compute(
|
||||
(batch, out_h, out_w, out_c),
|
||||
lambda n, h, w, co: te.sum(
|
||||
_dilate(n, h + rh + border_h, w + rw + border_w, rc).astype(out_dtype)
|
||||
* weight[filter_h - 1 - rh, filter_w - 1 - rw, rc, co].astype(out_dtype),
|
||||
axis=[rh, rw, rc],
|
||||
),
|
||||
name="conv2d_transpose_nhwc",
|
||||
)
|
||||
return (inputs, weight, output)
|
||||
|
||||
|
||||
def conv2d_capsule_nhwijc( # pylint: disable=invalid-name,missing-docstring
|
||||
N: int,
|
||||
H: int,
|
||||
W: int,
|
||||
CI: int,
|
||||
CO: int,
|
||||
kernel_size: int,
|
||||
stride: int = 1,
|
||||
padding: int = 0,
|
||||
capsule_size: int = 4,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
inputs = te.placeholder(
|
||||
(N, H, W, capsule_size, capsule_size, CI), name="inputs", dtype=in_dtype
|
||||
)
|
||||
weight = te.placeholder(
|
||||
(kernel_size, kernel_size, capsule_size, capsule_size, CI, CO),
|
||||
name="weight",
|
||||
dtype=in_dtype,
|
||||
)
|
||||
batch_size, in_h, in_w, _, _, in_channel = inputs.shape
|
||||
k_h, k_w, _, _, _, out_channel = weight.shape
|
||||
|
||||
out_h = (in_h + 2 * padding - kernel_size) // stride + 1
|
||||
out_w = (in_w + 2 * padding - kernel_size) // stride + 1
|
||||
|
||||
rh = te.reduce_axis((0, k_h), name="rh")
|
||||
rw = te.reduce_axis((0, k_w), name="rw")
|
||||
cap_k = te.reduce_axis((0, capsule_size), name="cap_k")
|
||||
rc = te.reduce_axis((0, in_channel), name="rc")
|
||||
|
||||
padded = topi.nn.pad(inputs, [0, padding, padding, 0, 0, 0])
|
||||
output = te.compute(
|
||||
(batch_size, out_h, out_w, capsule_size, capsule_size, out_channel),
|
||||
lambda n, h, w, cap_i, cap_j, co: te.sum(
|
||||
(
|
||||
padded[n, h * stride + rh, w * stride + rw, cap_i, cap_k, rc].astype(out_dtype)
|
||||
* weight[rh, rw, cap_k, cap_j, rc, co].astype(out_dtype)
|
||||
),
|
||||
axis=[rh, rw, cap_k, rc],
|
||||
),
|
||||
name="conv2d_capsule_nhwijc",
|
||||
)
|
||||
return (inputs, weight, output)
|
||||
|
||||
|
||||
def norm_bmn( # pylint: disable=invalid-name,missing-docstring
|
||||
B: int,
|
||||
M: int,
|
||||
N: int,
|
||||
) -> tuple[te.Tensor, te.Tensor]:
|
||||
a = te.placeholder((B, M, N), name="A")
|
||||
i = te.reduce_axis((0, M), name="i")
|
||||
j = te.reduce_axis((0, N), name="j")
|
||||
c = te.compute(
|
||||
(B,),
|
||||
lambda b: te.sum(a[b][i][j] * a[b][i][j], axis=[i, j]),
|
||||
name="C",
|
||||
)
|
||||
d = te.compute((B,), lambda b: te.sqrt(c[b]), name="D")
|
||||
return (a, d)
|
||||
|
||||
|
||||
def conv2d_nhwc_without_layout_rewrite( # pylint: disable=invalid-name
|
||||
Input: te.Tensor,
|
||||
Filter: te.Tensor,
|
||||
stride: int,
|
||||
padding: int,
|
||||
dilation: int,
|
||||
out_dtype="float32",
|
||||
):
|
||||
"""A copy of `topi.nn.conv2d_nhwc` but without the 'layout_free` attribute.
|
||||
We use this in single op and subgraph evaluation
|
||||
because we don't want to introduce graph level optimization.
|
||||
"""
|
||||
assert isinstance(stride, int) or len(stride) == 2
|
||||
assert isinstance(dilation, int) or len(dilation) == 2
|
||||
|
||||
if isinstance(stride, int):
|
||||
stride_h = stride_w = stride
|
||||
else:
|
||||
stride_h, stride_w = stride
|
||||
|
||||
if isinstance(dilation, int):
|
||||
dilation_h = dilation_w = dilation
|
||||
else:
|
||||
dilation_h, dilation_w = dilation
|
||||
|
||||
batch, in_height, in_width, in_channel = Input.shape # type: ignore
|
||||
kernel_h, kernel_w, _channel, num_filter = Filter.shape # type: ignore
|
||||
|
||||
# compute the output shape
|
||||
dilated_kernel_h = (kernel_h - 1) * dilation_h + 1
|
||||
dilated_kernel_w = (kernel_w - 1) * dilation_w + 1
|
||||
pad_top, pad_left, pad_down, pad_right = topi.nn.get_pad_tuple(
|
||||
padding, (dilated_kernel_h, dilated_kernel_w)
|
||||
)
|
||||
out_channel = num_filter
|
||||
out_height = topi.utils.simplify(
|
||||
(in_height - dilated_kernel_h + pad_top + pad_down) // stride_h + 1
|
||||
)
|
||||
out_width = topi.utils.simplify(
|
||||
(in_width - dilated_kernel_w + pad_left + pad_right) // stride_w + 1
|
||||
)
|
||||
pad_before = [0, pad_top, pad_left, 0]
|
||||
pad_after = [0, pad_down, pad_right, 0]
|
||||
PaddedInput = topi.nn.pad(Input, pad_before, pad_after, name="PaddedInput")
|
||||
rc = te.reduce_axis((0, in_channel), name="rc")
|
||||
ry = te.reduce_axis((0, kernel_h), name="ry")
|
||||
rx = te.reduce_axis((0, kernel_w), name="rx")
|
||||
Output = te.compute(
|
||||
(batch, out_height, out_width, out_channel),
|
||||
lambda nn, yy, xx, ff: te.sum(
|
||||
PaddedInput[
|
||||
nn, yy * stride_h + ry * dilation_h, xx * stride_w + rx * dilation_w, rc
|
||||
].astype(out_dtype)
|
||||
* Filter[ry, rx, rc, ff].astype(out_dtype), # type: ignore
|
||||
axis=[ry, rx, rc],
|
||||
),
|
||||
name="Conv2dOutput",
|
||||
tag="conv2d_nhwc",
|
||||
)
|
||||
return Output
|
||||
|
||||
|
||||
def conv2d_nhwc_bn_relu( # pylint: disable=invalid-name,missing-docstring
|
||||
N: int,
|
||||
H: int,
|
||||
W: int,
|
||||
CI: int,
|
||||
CO: int,
|
||||
kernel_size: int,
|
||||
strides: int,
|
||||
padding: int,
|
||||
dilation: int = 1,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor, te.Tensor, te.Tensor, te.Tensor]:
|
||||
data = te.placeholder((N, H, W, CI), name="data", dtype=in_dtype)
|
||||
kernel = te.placeholder((kernel_size, kernel_size, CI, CO), name="kernel", dtype=in_dtype)
|
||||
bias = te.placeholder((CO,), name="bias")
|
||||
bn_scale = te.placeholder((CO,), name="bn_scale")
|
||||
bn_offset = te.placeholder((CO,), name="bn_offset")
|
||||
OH = (H + 2 * padding - (kernel_size - 1) * dilation - 1) // strides + 1
|
||||
OW = (W + 2 * padding - (kernel_size - 1) * dilation - 1) // strides + 1
|
||||
conv = conv2d_nhwc_without_layout_rewrite(data, kernel, strides, padding, dilation, out_dtype)
|
||||
conv = te.compute(
|
||||
(N, OH, OW, CO), lambda i, j, k, l: conv[i, j, k, l] + bias[l], name="bias_add"
|
||||
)
|
||||
conv = te.compute(
|
||||
(N, OH, OW, CO), lambda i, j, k, l: conv[i, j, k, l] * bn_scale[l], name="bn_mul"
|
||||
)
|
||||
conv = te.compute(
|
||||
(N, OH, OW, CO), lambda i, j, k, l: conv[i, j, k, l] + bn_offset[l], name="bn_add"
|
||||
)
|
||||
out = topi.nn.relu(conv)
|
||||
return (data, kernel, bias, bn_offset, bn_scale, out)
|
||||
|
||||
|
||||
def transpose_batch_matmul( # pylint: disable=invalid-name,missing-docstring
|
||||
batch: int,
|
||||
seq_len: int,
|
||||
n_head: int,
|
||||
n_dim: int,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
query = te.placeholder((batch, seq_len, n_head, n_dim), name="query", dtype=in_dtype)
|
||||
value = te.placeholder((batch, seq_len, n_head, n_dim), name="value", dtype=in_dtype)
|
||||
query_T = te.compute(
|
||||
(batch, n_head, seq_len, n_dim),
|
||||
lambda b, h, l, d: query[b, l, h, d],
|
||||
name="query_T",
|
||||
)
|
||||
value_T = te.compute(
|
||||
(batch, n_head, n_dim, seq_len),
|
||||
lambda b, h, d, l: value[b, l, h, d],
|
||||
name="value_T",
|
||||
)
|
||||
k = te.reduce_axis((0, n_dim), name="k")
|
||||
out = te.compute(
|
||||
(batch, n_head, seq_len, seq_len),
|
||||
lambda b, h, i, j: te.sum(
|
||||
query_T[b, h, i, k].astype(out_dtype) * value_T[b, h, k, j].astype(out_dtype), axis=[k]
|
||||
),
|
||||
name="C",
|
||||
)
|
||||
return (query, value, out)
|
||||
|
||||
|
||||
def conv2d_winograd_nhwc( # pylint: disable=invalid-name,missing-docstring
|
||||
N: int,
|
||||
H: int,
|
||||
W: int,
|
||||
CI: int,
|
||||
CO: int,
|
||||
kernel_size: int,
|
||||
stride: int = 1,
|
||||
padding: int = 0,
|
||||
dilation: int = 1,
|
||||
tile_size: int = 4,
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
from tvm.topi.nn.conv2d import ( # pylint: disable=import-outside-toplevel
|
||||
_conv2d_winograd_nhwc_impl,
|
||||
)
|
||||
|
||||
target = Target.current(allow_none=True)
|
||||
if target is not None and target.kind.name == "cuda":
|
||||
write_cache_level = 3
|
||||
else:
|
||||
write_cache_level = 2
|
||||
data = te.placeholder((N, H, W, CI), "float32", name="data")
|
||||
weight = te.placeholder((kernel_size, kernel_size, CO, CI), "float32", name="weight")
|
||||
out = _conv2d_winograd_nhwc_impl(
|
||||
data,
|
||||
weight,
|
||||
stride,
|
||||
padding,
|
||||
dilation,
|
||||
"float32",
|
||||
pre_computed=True,
|
||||
auto_scheduler_rewritten_layout="",
|
||||
meta_schedule_original_shape=None,
|
||||
tile_size=tile_size,
|
||||
write_cache_level=write_cache_level,
|
||||
)
|
||||
return (data, weight, out)
|
||||
|
||||
|
||||
def matmul(
|
||||
n: int, m: int, k: int, in_dtype: str = "float32", out_dtype: str = "float32"
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
a = te.placeholder((n, k), name="A", dtype=in_dtype)
|
||||
b = te.placeholder((k, m), name="B", dtype=in_dtype)
|
||||
k = te.reduce_axis((0, k), name="k")
|
||||
c = te.compute(
|
||||
(n, m),
|
||||
lambda i, j: te.sum(a[i, k].astype(out_dtype) * b[k, j].astype(out_dtype), axis=[k]),
|
||||
name="C",
|
||||
)
|
||||
return (a, b, c)
|
||||
|
||||
|
||||
def matmul_relu(
|
||||
n: int, m: int, k: int, in_dtype: str = "float32", out_dtype: str = "float32"
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
a = te.placeholder((n, k), name="A", dtype=in_dtype)
|
||||
b = te.placeholder((k, m), name="B", dtype=in_dtype)
|
||||
k = te.reduce_axis((0, k), name="k")
|
||||
c = te.compute(
|
||||
(n, m),
|
||||
lambda i, j: te.sum(a[i, k].astype(out_dtype) * b[k, j].astype(out_dtype), axis=[k]),
|
||||
name="C",
|
||||
)
|
||||
d = topi.nn.relu(c) # pylint: disable=invalid-name
|
||||
return (a, b, d)
|
||||
|
||||
|
||||
def conv2d_nchw( # pylint: disable=invalid-name
|
||||
n: int,
|
||||
h: int,
|
||||
w: int,
|
||||
ci: int,
|
||||
co: int,
|
||||
kh: int,
|
||||
kw: int,
|
||||
stride: int,
|
||||
padding: int,
|
||||
dilation: int = 1,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
x = te.placeholder((n, ci, h, w), name="X", dtype=in_dtype)
|
||||
w = te.placeholder((co, ci, kh, kw), name="W", dtype=in_dtype)
|
||||
y = topi.nn.conv2d_nchw(
|
||||
Input=x, Filter=w, stride=stride, padding=padding, dilation=dilation, out_dtype=out_dtype
|
||||
)
|
||||
return (x, w, y)
|
||||
|
||||
|
||||
def conv2d_nchw_bias_bn_relu( # pylint: disable=invalid-name
|
||||
n: int,
|
||||
h: int,
|
||||
w: int,
|
||||
ci: int,
|
||||
co: int,
|
||||
kh: int,
|
||||
kw: int,
|
||||
stride: int,
|
||||
padding: int,
|
||||
dilation: int = 1,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor, te.Tensor, te.Tensor, te.Tensor]:
|
||||
oh = (h + 2 * padding - (kh - 1) * dilation - 1) // stride + 1 # pylint: disable=invalid-name
|
||||
ow = (w + 2 * padding - (kw - 1) * dilation - 1) // stride + 1 # pylint: disable=invalid-name
|
||||
x = te.placeholder((n, ci, h, w), name="X", dtype=in_dtype)
|
||||
w = te.placeholder((co, ci, kh, kw), name="W", dtype=in_dtype)
|
||||
b = te.placeholder((co, 1, 1), name="B", dtype=out_dtype)
|
||||
bn_scale = te.placeholder((co, 1, 1), name="bn_scale", dtype=out_dtype)
|
||||
bn_offset = te.placeholder((co, 1, 1), name="bn_offset", dtype=out_dtype)
|
||||
y = topi.nn.conv2d_nchw(
|
||||
Input=x, Filter=w, stride=stride, padding=padding, dilation=dilation, out_dtype=out_dtype
|
||||
)
|
||||
y = te.compute((n, co, oh, ow), lambda i, j, k, l: y[i, j, k, l] + b[j, 0, 0], name="bias_add")
|
||||
y = te.compute(
|
||||
(n, co, oh, ow), lambda i, j, k, l: y[i, j, k, l] * bn_scale[j, 0, 0], name="bn_mul"
|
||||
)
|
||||
y = te.compute(
|
||||
(n, co, oh, ow), lambda i, j, k, l: y[i, j, k, l] + bn_offset[j, 0, 0], name="bn_add"
|
||||
)
|
||||
y = topi.nn.relu(y)
|
||||
return (x, w, b, bn_scale, bn_offset, y)
|
||||
|
||||
|
||||
def max_pool2d_nchw( # pylint: disable=invalid-name
|
||||
n: int,
|
||||
h: int,
|
||||
w: int,
|
||||
ci: int,
|
||||
padding: int,
|
||||
) -> tuple[te.Tensor, te.Tensor]: # pylint: disable=invalid-name
|
||||
x = te.placeholder((n, ci, h, w), name="X")
|
||||
y = topi.nn.pool2d(x, [2, 2], [1, 1], [1, 1], [padding, padding, padding, padding], "max")
|
||||
return (x, y)
|
||||
|
||||
|
||||
def softmax_mn(m, n) -> tuple[te.Tensor, te.Tensor]: # pylint: disable=invalid-name
|
||||
a = te.placeholder((m, n), name="A")
|
||||
b = topi.nn.softmax(a, axis=1)
|
||||
|
||||
return (a, b)
|
||||
|
||||
|
||||
def create_te_workload(name: str, idx: int) -> tirx.PrimFunc:
|
||||
workload_func, params = CONFIGS[name]
|
||||
return te.create_prim_func(workload_func(*params[idx])) # type: ignore
|
||||
|
||||
|
||||
CONFIGS = {
|
||||
"C1D": (
|
||||
conv1d_nlc,
|
||||
[
|
||||
# derived from conv2d_shapes
|
||||
(1, 256, 64, 128, 3, 2, 1),
|
||||
# (1, 256, 64, 128, 1, 2, 0),
|
||||
# (1, 256, 64, 64, 1, 1, 0),
|
||||
# (1, 128, 128, 256, 3, 2, 1),
|
||||
(1, 128, 128, 256, 1, 2, 0),
|
||||
# (1, 128, 128, 128, 3, 1, 1),
|
||||
# (1, 64, 256, 512, 3, 2, 1),
|
||||
# (1, 64, 256, 512, 1, 2, 0),
|
||||
(1, 64, 256, 256, 5, 1, 2),
|
||||
(1, 32, 512, 512, 3, 1, 1),
|
||||
],
|
||||
),
|
||||
"C2D": (
|
||||
conv2d_nhwc,
|
||||
[
|
||||
# all conv2d layers in resnet-18
|
||||
(1, 224, 224, 3, 64, 7, 2, 3),
|
||||
# (1, 56, 56, 64, 128, 3, 2, 1),
|
||||
# (1, 56, 56, 64, 128, 1, 2, 0),
|
||||
# (1, 56, 56, 64, 64, 3, 1, 1),
|
||||
(1, 56, 56, 64, 64, 1, 1, 0),
|
||||
# (1, 28, 28, 128, 256, 3, 2, 1),
|
||||
# (1, 28, 28, 128, 256, 1, 2, 0),
|
||||
# (1, 28, 28, 128, 128, 3, 1, 1),
|
||||
# (1, 14, 14, 256, 512, 3, 2, 1),
|
||||
# (1, 14, 14, 256, 512, 1, 2, 0),
|
||||
(1, 14, 14, 256, 256, 3, 1, 1),
|
||||
(1, 7, 7, 512, 512, 3, 1, 1),
|
||||
],
|
||||
),
|
||||
"C3D": (
|
||||
conv3d_ndhwc,
|
||||
[
|
||||
# Derived from conv2d_shapes. Use depth=16 for all configurations
|
||||
(1, 16, 224, 224, 3, 64, 7, 2, 3),
|
||||
# (1, 16, 56, 56, 64, 128, 3, 2, 1),
|
||||
# (1, 16, 56, 56, 64, 128, 1, 2, 0),
|
||||
# (1, 16, 56, 56, 64, 64, 3, 1, 1),
|
||||
(1, 16, 56, 56, 64, 64, 1, 1, 0),
|
||||
# (1, 16, 28, 28, 128, 256, 3, 2, 1),
|
||||
# (1, 16, 28, 28, 128, 256, 1, 2, 0),
|
||||
# (1, 16, 28, 28, 128, 128, 3, 1, 1),
|
||||
# (1, 16, 14, 14, 256, 512, 3, 2, 1),
|
||||
# (1, 16, 14, 14, 256, 512, 1, 2, 0),
|
||||
(1, 16, 14, 14, 256, 256, 3, 1, 1),
|
||||
(1, 16, 7, 7, 512, 512, 3, 1, 1),
|
||||
],
|
||||
),
|
||||
"GMM": (
|
||||
batch_matmul_nkkm,
|
||||
[
|
||||
(1, 128, 128, 128),
|
||||
(1, 512, 32, 512),
|
||||
(1, 512, 512, 512),
|
||||
(1, 1024, 1024, 1024),
|
||||
],
|
||||
),
|
||||
"GRP": (
|
||||
conv2d_nhwc,
|
||||
[
|
||||
# Derived from conv2d_shapes. Use group=4 for all configurations
|
||||
(1, 56, 56, 64, 128, 3, 2, 1, 1, 4),
|
||||
# (1, 56, 56, 64, 128, 1, 2, 0 , 1, 4),
|
||||
# (1, 56, 56, 64, 64, 3, 1, 1 , 1, 4),
|
||||
(1, 56, 56, 64, 64, 1, 1, 0, 1, 4),
|
||||
# (1, 28, 28, 128, 256, 3, 2, 1, 1, 4),
|
||||
# (1, 28, 28, 128, 256, 1, 2, 0, 1, 4),
|
||||
# (1, 28, 28, 128, 128, 3, 1, 1, 1, 4),
|
||||
# (1, 14, 14, 256, 512, 3, 2, 1, 1, 4),
|
||||
# (1, 14, 14, 256, 512, 1, 2, 0, 1, 4),
|
||||
(1, 14, 14, 256, 256, 3, 1, 1, 1, 4),
|
||||
(1, 7, 7, 512, 512, 3, 1, 1, 1, 4),
|
||||
],
|
||||
),
|
||||
"DIL": (
|
||||
conv2d_nhwc,
|
||||
[
|
||||
# Derived from conv2d_shapes. Use dilation=2 for all configurations
|
||||
(1, 224, 224, 3, 64, 7, 2, 3, 2),
|
||||
# (1, 56, 56, 64, 128, 3, 2, 1 , 2),
|
||||
# (1, 56, 56, 64, 128, 1, 2, 0 , 2),
|
||||
# (1, 56, 56, 64, 64, 3, 1, 1 , 2),
|
||||
(1, 56, 56, 64, 64, 1, 1, 0, 2),
|
||||
# (1, 28, 28, 128, 256, 3, 2, 1, 2),
|
||||
# (1, 28, 28, 128, 256, 1, 2, 0, 2),
|
||||
# (1, 28, 28, 128, 128, 3, 1, 1, 2),
|
||||
# (1, 14, 14, 256, 512, 3, 2, 1, 2),
|
||||
# (1, 14, 14, 256, 512, 1, 2, 0, 2),
|
||||
(1, 14, 14, 256, 256, 3, 1, 1, 2),
|
||||
(1, 7, 7, 512, 512, 3, 1, 1, 2),
|
||||
],
|
||||
),
|
||||
"DEP": (
|
||||
depthwise_conv2d_nhwc,
|
||||
[
|
||||
# all depthwise conv2d layers in mobilenet
|
||||
(1, 112, 112, 32, 3, 1, 1),
|
||||
(1, 112, 112, 64, 3, 2, 1),
|
||||
# (1, 56, 56, 128, 3, 1, 1),
|
||||
# (1, 56, 56, 128, 3, 2, 1),
|
||||
# (1, 28, 28, 256, 3, 1, 1),
|
||||
# (1, 28, 28, 256, 3, 2, 1),
|
||||
# (1, 14, 14, 512, 3, 1, 1),
|
||||
(1, 14, 14, 512, 3, 2, 1),
|
||||
(1, 7, 7, 1024, 3, 1, 1),
|
||||
],
|
||||
),
|
||||
"T2D": (
|
||||
conv2d_transpose_nhwc,
|
||||
[
|
||||
# all conv2d transpose layers in DCGAN
|
||||
(1, 4, 4, 512, 256, 4, 2, 1),
|
||||
(1, 8, 8, 256, 128, 4, 2, 1),
|
||||
(1, 16, 16, 128, 64, 4, 2, 1),
|
||||
(1, 32, 32, 64, 3, 4, 2, 1),
|
||||
],
|
||||
),
|
||||
"CAP": (
|
||||
conv2d_capsule_nhwijc,
|
||||
[
|
||||
# all conv2d capsule layers in matrix capsules withemrouting (ICLR 2018)
|
||||
(1, 16, 16, 32, 32, 3, 2, 1),
|
||||
(1, 8, 8, 32, 32, 3, 1, 1),
|
||||
(1, 16, 16, 8, 16, 3, 2, 1),
|
||||
(1, 8, 8, 16, 16, 3, 1, 1),
|
||||
],
|
||||
),
|
||||
"NRM": (
|
||||
norm_bmn,
|
||||
[
|
||||
(1, 256, 256),
|
||||
(1, 512, 512),
|
||||
(1, 1024, 1024),
|
||||
(1, 4096, 1024),
|
||||
],
|
||||
),
|
||||
"SFM": (
|
||||
softmax_mn,
|
||||
[
|
||||
(256, 256),
|
||||
(512, 512),
|
||||
(1024, 1024),
|
||||
(2048, 2048),
|
||||
],
|
||||
),
|
||||
"CBR": (
|
||||
conv2d_nhwc_bn_relu,
|
||||
[
|
||||
(1, 224, 224, 3, 64, 7, 2, 3),
|
||||
(1, 56, 56, 64, 128, 3, 2, 1),
|
||||
(1, 28, 28, 128, 256, 1, 2, 0),
|
||||
(1, 7, 7, 512, 512, 3, 1, 1),
|
||||
],
|
||||
),
|
||||
"TBG": (
|
||||
transpose_batch_matmul,
|
||||
[
|
||||
(1, 128, 12, 64),
|
||||
(1, 128, 16, 64),
|
||||
(1, 64, 12, 128),
|
||||
(1, 128, 12, 128),
|
||||
],
|
||||
),
|
||||
"C2D_WIN_NHWC": (
|
||||
conv2d_winograd_nhwc,
|
||||
[
|
||||
(1, 14, 14, 128, 128, 6),
|
||||
],
|
||||
),
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
# 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.
|
||||
# pylint: disable=missing-docstring
|
||||
# ruff: noqa: F821
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
import tvm
|
||||
from tvm.s_tir import meta_schedule as ms
|
||||
from tvm.s_tir.meta_schedule.testing.te_workload import create_te_workload
|
||||
from tvm.support import describe
|
||||
from tvm.testing.utils import strtobool
|
||||
|
||||
|
||||
def _parse_args():
|
||||
args = argparse.ArgumentParser()
|
||||
args.add_argument(
|
||||
"--workload",
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
args.add_argument(
|
||||
"--target",
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
args.add_argument(
|
||||
"--num-trials",
|
||||
type=int,
|
||||
required=True,
|
||||
)
|
||||
args.add_argument(
|
||||
"--rpc-host",
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
args.add_argument(
|
||||
"--rpc-port",
|
||||
type=int,
|
||||
required=True,
|
||||
)
|
||||
args.add_argument(
|
||||
"--rpc-key",
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
args.add_argument(
|
||||
"--work-dir",
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
args.add_argument(
|
||||
"--number",
|
||||
type=int,
|
||||
default=3,
|
||||
)
|
||||
args.add_argument(
|
||||
"--repeat",
|
||||
type=int,
|
||||
default=1,
|
||||
)
|
||||
args.add_argument(
|
||||
"--min-repeat-ms",
|
||||
type=int,
|
||||
default=100,
|
||||
)
|
||||
args.add_argument(
|
||||
"--adaptive-training",
|
||||
type=lambda x: bool(strtobool(x)),
|
||||
required=False,
|
||||
help="example: True / False",
|
||||
default=True,
|
||||
)
|
||||
args.add_argument(
|
||||
"--cpu-flush",
|
||||
type=lambda x: bool(strtobool(x)),
|
||||
help="example: True / False",
|
||||
required=True,
|
||||
)
|
||||
parsed = args.parse_args()
|
||||
parsed.target = tvm.target.Target(parsed.target)
|
||||
parsed.rpc_config = ms.runner.RPCConfig(
|
||||
tracker_host=parsed.rpc_host,
|
||||
tracker_port=parsed.rpc_port,
|
||||
tracker_key=parsed.rpc_key,
|
||||
session_timeout_sec=60,
|
||||
)
|
||||
return parsed
|
||||
|
||||
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s.%(msecs)03d %(levelname)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
logging.getLogger("tvm.s_tir.meta_schedule").setLevel(logging.DEBUG)
|
||||
ARGS = _parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
describe()
|
||||
print(f"Workload: {ARGS.workload}")
|
||||
with ms.Profiler() as profiler:
|
||||
sch: s_tir.Schedule | None = ms.tir_integration.tune_tir(
|
||||
mod=create_te_workload(ARGS.workload, 0),
|
||||
target=ARGS.target,
|
||||
work_dir=ARGS.work_dir,
|
||||
max_trials_global=ARGS.num_trials,
|
||||
num_trials_per_iter=64,
|
||||
runner=ms.runner.RPCRunner( # type: ignore
|
||||
rpc_config=ARGS.rpc_config,
|
||||
evaluator_config=ms.runner.EvaluatorConfig(
|
||||
number=ARGS.number,
|
||||
repeat=ARGS.repeat,
|
||||
min_repeat_ms=ARGS.min_repeat_ms,
|
||||
enable_cpu_cache_flush=ARGS.cpu_flush,
|
||||
),
|
||||
alloc_repeat=1,
|
||||
),
|
||||
cost_model=ms.cost_model.XGBModel( # type: ignore
|
||||
extractor=ms.feature_extractor.PerStoreFeature(),
|
||||
adaptive_training=ARGS.adaptive_training,
|
||||
),
|
||||
strategy=ms.search_strategy.EvolutionarySearch(),
|
||||
)
|
||||
|
||||
print("Tuning Time:")
|
||||
print(profiler.table())
|
||||
|
||||
if sch is None:
|
||||
print("No valid schedule found!")
|
||||
else:
|
||||
print(sch.mod.script())
|
||||
print(sch.trace)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,113 @@
|
||||
# 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.
|
||||
"""Testing utility functions in meta schedule"""
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
import numpy as np # type: ignore
|
||||
|
||||
import tvm
|
||||
from tvm.runtime import Tensor
|
||||
|
||||
|
||||
def generate_input_data(
|
||||
input_shape: list[int],
|
||||
input_dtype: str,
|
||||
*,
|
||||
low: int | None = None,
|
||||
high: int | None = None,
|
||||
) -> np.ndarray:
|
||||
"""Generate input date with given shape and data type.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
input_shape : List[int]
|
||||
The shape of the input data.
|
||||
input_dtype : str
|
||||
The data type of the input date.
|
||||
|
||||
Returns
|
||||
-------
|
||||
input_data : np.ndarray
|
||||
The generated input data with given shape and data type in numpy ndarray.
|
||||
"""
|
||||
if input_dtype.startswith("float"):
|
||||
return np.random.uniform(size=input_shape).astype(input_dtype)
|
||||
range_map = {
|
||||
"uint8": (0, 255),
|
||||
"int8": (-128, 127),
|
||||
"int32": (0, 10000),
|
||||
"uint32": (0, 10000),
|
||||
"int64": (0, 10000),
|
||||
"uint64": (0, 10000),
|
||||
}
|
||||
if input_dtype in range_map:
|
||||
_low, _high = range_map[input_dtype]
|
||||
return np.random.randint(
|
||||
low=_low if low is None else low,
|
||||
high=_high if high is None else high,
|
||||
size=input_shape,
|
||||
dtype=input_dtype,
|
||||
)
|
||||
raise ValueError("Unsupported input datatype!")
|
||||
|
||||
|
||||
def create_calculator(backend: str) -> Callable:
|
||||
"""Create a function to fetch the computing result of running the given runtime module.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
backend : str
|
||||
The backend to use, only tirx is supported for now.
|
||||
|
||||
Returns
|
||||
-------
|
||||
func : Callable
|
||||
The function to fetch the computing result.
|
||||
"""
|
||||
|
||||
def f_calculator(
|
||||
rt_mod: tvm.runtime.Module,
|
||||
dev: tvm.runtime.Device, # pylint: disable=unused-argument
|
||||
input_data: dict[str, Tensor],
|
||||
) -> list[Tensor]:
|
||||
"""Fetch the result of running the given runtime module.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
rt_mod : tvm.runtime.Module
|
||||
The runtime module.
|
||||
dev : tvm.device
|
||||
The device type to run workload.
|
||||
input_data : Dict[str, np.ndarray]
|
||||
The input data as a dictionary.
|
||||
"""
|
||||
try:
|
||||
if backend == "tirx":
|
||||
data = [v for _, v in sorted(input_data.items(), key=lambda x: x[0])]
|
||||
rt_mod(*data)
|
||||
return data
|
||||
else:
|
||||
raise ValueError(f"Backend {backend} not supported in f_calculator!")
|
||||
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
print(
|
||||
f"Run module f_calculator via RPC failed, exception: {exc}",
|
||||
)
|
||||
return None
|
||||
|
||||
return f_calculator
|
||||
@@ -0,0 +1,784 @@
|
||||
# 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.
|
||||
# ruff: noqa: F403
|
||||
"""JSON Database validation script"""
|
||||
|
||||
import argparse
|
||||
import itertools
|
||||
import logging
|
||||
import warnings
|
||||
from collections.abc import Callable
|
||||
from statistics import mean
|
||||
from typing import Any
|
||||
|
||||
import numpy as np # type: ignore
|
||||
import tvm_ffi
|
||||
from tvm_ffi import get_global_func, register_global_func
|
||||
|
||||
import tvm
|
||||
from tvm.ir import IRModule
|
||||
from tvm.s_tir import Schedule
|
||||
from tvm.s_tir import meta_schedule as ms
|
||||
from tvm.s_tir.meta_schedule.testing.tune_utils import generate_input_data
|
||||
from tvm.s_tir.meta_schedule.utils import remove_build_dir
|
||||
from tvm.s_tir.schedule import Trace
|
||||
from tvm.s_tir.tensor_intrin import * # type: ignore # pylint: disable=wildcard-import,unused-wildcard-import
|
||||
from tvm.support import describe
|
||||
from tvm.target import Target
|
||||
from tvm.testing.utils import strtobool
|
||||
|
||||
DELIMITOR = "\n" + "-" * 30 + "\n"
|
||||
|
||||
|
||||
def _parse_args():
|
||||
args = argparse.ArgumentParser()
|
||||
args.add_argument(
|
||||
"--work-dir",
|
||||
type=str,
|
||||
required=True,
|
||||
help="The path to the work directory containing database files.",
|
||||
)
|
||||
args.add_argument(
|
||||
"--target",
|
||||
type=Target,
|
||||
required=True,
|
||||
)
|
||||
args.add_argument(
|
||||
"--baseline-target",
|
||||
type=Target,
|
||||
default='{"kind": "llvm", "num-cores": 1}',
|
||||
required=False,
|
||||
help="The baseline target to compile the original module.",
|
||||
)
|
||||
args.add_argument(
|
||||
"--top-k",
|
||||
type=int,
|
||||
default=10**9,
|
||||
required=False,
|
||||
help="The number of top-k tuning records to validate for each unique original workload.",
|
||||
)
|
||||
args.add_argument(
|
||||
"--rpc-host",
|
||||
type=str,
|
||||
)
|
||||
args.add_argument(
|
||||
"--rpc-port",
|
||||
type=int,
|
||||
)
|
||||
args.add_argument(
|
||||
"--rpc-key",
|
||||
type=str,
|
||||
)
|
||||
args.add_argument(
|
||||
"--number",
|
||||
type=int,
|
||||
default=3,
|
||||
)
|
||||
args.add_argument(
|
||||
"--repeat",
|
||||
type=int,
|
||||
default=1,
|
||||
)
|
||||
args.add_argument(
|
||||
"--min-repeat-ms",
|
||||
type=int,
|
||||
default=100,
|
||||
)
|
||||
args.add_argument(
|
||||
"--cpu-flush",
|
||||
type=lambda x: bool(strtobool(x)),
|
||||
help="example: True / False",
|
||||
required=True,
|
||||
)
|
||||
args.add_argument(
|
||||
"--input-generator-func",
|
||||
type=str,
|
||||
default="tvm.s_tir.meta_schedule.testing.default_input_generator",
|
||||
)
|
||||
args.add_argument(
|
||||
"--check-metric-func",
|
||||
type=str,
|
||||
default="tvm.s_tir.meta_schedule.testing.default_check_metric",
|
||||
)
|
||||
parsed = args.parse_args()
|
||||
parsed.target = tvm.target.Target(parsed.target)
|
||||
if parsed.rpc_host is not None and parsed.rpc_port is not None and parsed.rpc_key is not None:
|
||||
parsed.rpc_config = ms.runner.RPCConfig(
|
||||
tracker_host=parsed.rpc_host,
|
||||
tracker_port=parsed.rpc_port,
|
||||
tracker_key=parsed.rpc_key,
|
||||
session_timeout_sec=600,
|
||||
)
|
||||
else:
|
||||
parsed.rpc_config = None
|
||||
warnings.warn("RPC config is not provided, will use local runner.")
|
||||
if parsed.cpu_flush and parsed.target.kind.name != "llvm":
|
||||
warnings.warn("cpu_flush is only supported on llvm target")
|
||||
return parsed
|
||||
|
||||
|
||||
# arg parser
|
||||
ARGS = _parse_args()
|
||||
|
||||
# logging
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s.%(msecs)03d %(levelname)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
logging.getLogger("tvm.s_tir.meta_schedule").setLevel(logging.DEBUG)
|
||||
logging.getLogger("tvm.s_tir.meta_schedule.runner").setLevel(logging.WARN)
|
||||
|
||||
|
||||
def get_device_type(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 get_runtime_device(target: Target) -> tvm.runtime.Device:
|
||||
"""Get the runtime device from a target.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
target : Target
|
||||
The target to get the runtime device from.
|
||||
|
||||
Returns
|
||||
-------
|
||||
device : tvm.runtime.Device
|
||||
The runtime device.
|
||||
"""
|
||||
if target.kind.name == "llvm":
|
||||
return tvm.cpu()
|
||||
elif target.kind.name == "cuda":
|
||||
return tvm.cuda()
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported target kind for runtime device: {target.kind.name}")
|
||||
|
||||
|
||||
def check_and_run(func: str | Callable, *args, **kwargs) -> Any:
|
||||
"""Check if the function is a string or a callable, and run it."""
|
||||
if isinstance(func, str):
|
||||
func = get_global_func(func)
|
||||
return func(*args, **kwargs) # type: ignore
|
||||
|
||||
|
||||
class OriginalModule:
|
||||
"""Original module class for deduplication."""
|
||||
|
||||
def __init__(self, mod: IRModule):
|
||||
self.mod = mod
|
||||
|
||||
def __eq__(self, __o: "OriginalModule") -> bool: # type: ignore
|
||||
return tvm_ffi.structural_equal(self.mod, __o.mod)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return tvm_ffi.structural_hash(self.mod)
|
||||
|
||||
|
||||
def initializer() -> None:
|
||||
"""Initializer function to register the functions on PopenWorker."""
|
||||
|
||||
@register_global_func("tvm.s_tir.meta_schedule.testing.default_check_metric")
|
||||
def default_check_metric( # pylint: disable=unused-variable,unreachable-code
|
||||
lhs: list[tvm.runtime.Tensor], rhs: list[tvm.runtime.Tensor]
|
||||
) -> bool:
|
||||
"""Check if the outputs are equal
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lhs : List[tvm.runtime.Tensor]
|
||||
The first list of Tensors to compare.
|
||||
|
||||
rhs : List[tvm.runtime.Tensor]
|
||||
The second list of Tensors to compare.
|
||||
|
||||
Returns
|
||||
-------
|
||||
is_equal : bool
|
||||
Whether the two lists of Tensors are equal.
|
||||
"""
|
||||
assert len(lhs) == len(rhs), "Different number of outputs from two modules"
|
||||
for i in range(len(lhs)): # pylint: disable=consider-using-enumerate
|
||||
if not np.allclose(lhs[i].numpy(), rhs[i].numpy(), rtol=1e-3, atol=2e-3):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@register_global_func("tvm.s_tir.meta_schedule.testing.default_input_generator")
|
||||
def default_input_generator( # pylint: disable=unused-variable
|
||||
mod: IRModule,
|
||||
) -> list[tvm.runtime.Tensor]:
|
||||
"""Default input generator function
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : IRModule
|
||||
The IRModule to generate the input data for.
|
||||
|
||||
Returns
|
||||
-------
|
||||
inputs : List[tvm.runtime.Tensor]
|
||||
The generated input data.
|
||||
"""
|
||||
|
||||
args_info = ms.arg_info.TensorInfo.from_prim_func(mod["main"])
|
||||
inputs = [
|
||||
tvm.runtime.tensor(
|
||||
generate_input_data(input_shape=arg_info.shape, input_dtype=arg_info.dtype)
|
||||
)
|
||||
for arg_info in args_info
|
||||
]
|
||||
return inputs
|
||||
|
||||
|
||||
def to_numpy(a: list[tvm.runtime.Tensor]) -> list[np.ndarray]:
|
||||
"""Convert a list of TVM Tensor to a list of numpy array
|
||||
|
||||
Parameters
|
||||
----------
|
||||
a : List[tvm.runtime.Tensor]
|
||||
The list of TVM Tensor to be converted
|
||||
|
||||
Returns
|
||||
-------
|
||||
b : List[np.ndarray]
|
||||
The list of numpy array
|
||||
"""
|
||||
assert a is not None, "Empty result cannot be converted to numpy"
|
||||
return [x.numpy() for x in a]
|
||||
|
||||
|
||||
def to_tvm_tensor(a: list[np.ndarray]) -> list[tvm.runtime.Tensor]:
|
||||
"""Convert a list of numpy array to a list of TVM Tensor
|
||||
|
||||
Parameters
|
||||
----------
|
||||
a : List[np.ndarray]
|
||||
The list of numpy array to be converted.
|
||||
|
||||
Returns
|
||||
-------
|
||||
b : List[tvm.runtime.Tensor]
|
||||
The list of TVM Tensor.
|
||||
"""
|
||||
assert a is not None, "Empty result cannot be converted to TVM Tensor"
|
||||
return [tvm.runtime.tensor(x) for x in a]
|
||||
|
||||
|
||||
def is_failed_record(record: ms.database.TuningRecord) -> bool:
|
||||
"""Check if a tuning record is failed.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
record : TuningRecord
|
||||
The tuning record to check.
|
||||
|
||||
Returns
|
||||
-------
|
||||
is_failed : bool
|
||||
"""
|
||||
return len(record.run_secs) == 1 and record.run_secs[0] == 1e9
|
||||
|
||||
|
||||
def print_with_counter_func(counter: int, total: int) -> Callable:
|
||||
"""Print with counter
|
||||
|
||||
Parameters
|
||||
----------
|
||||
counter : int
|
||||
The counter to print with.
|
||||
total : int
|
||||
The total number of items to print with.
|
||||
|
||||
Returns
|
||||
-------
|
||||
print_result : Callable
|
||||
The print result function.
|
||||
"""
|
||||
|
||||
def print_result(
|
||||
result: str,
|
||||
*,
|
||||
original_mod: IRModule = None,
|
||||
scheduled_mod: IRModule = None,
|
||||
inputs: list[np.ndarray] | None = None,
|
||||
original_res: list[np.ndarray] | None = None,
|
||||
scheduled_res: list[np.ndarray] | None = None,
|
||||
original_run_secs: list[float] | None = None,
|
||||
scheduled_run_secs: list[float] | None = None,
|
||||
exception: Exception | None = None,
|
||||
trace: str | None = None,
|
||||
) -> None:
|
||||
"""Print the validation result."""
|
||||
status = f"Progress {counter: 6d} / {total: 6d} (estimated) checked, result: {result:>10}, "
|
||||
|
||||
if result in ["pass", "wrong answer"]:
|
||||
status += (
|
||||
f"original: {mean(original_run_secs) * 1e3: 10.3f} ms, "
|
||||
f"scheduled: {mean(scheduled_run_secs) * 1e3: 10.3f} ms"
|
||||
)
|
||||
|
||||
output = [status]
|
||||
if result not in ["pass", "skip"]:
|
||||
output.extend(
|
||||
[
|
||||
"Original IRModule:" + DELIMITOR + original_mod.script(),
|
||||
"Scheduled IRModule:" + DELIMITOR + scheduled_mod.script(),
|
||||
"Trace" + DELIMITOR + str(trace),
|
||||
]
|
||||
)
|
||||
if result == "wrong answer":
|
||||
output.extend(
|
||||
[
|
||||
"Input:" + DELIMITOR + str(inputs),
|
||||
"Original Result:" + DELIMITOR + str(original_res),
|
||||
"Scheduled Result:" + DELIMITOR + str(scheduled_res),
|
||||
"Max Diff:"
|
||||
+ DELIMITOR
|
||||
+ str(
|
||||
[
|
||||
np.max(np.abs(original_res[i] - scheduled_res[i]))
|
||||
for i in range(len(original_res))
|
||||
]
|
||||
)
|
||||
+ "\n",
|
||||
]
|
||||
)
|
||||
elif result == "exception":
|
||||
output.extend(["Exception:" + DELIMITOR + str(exception) + "\n"])
|
||||
else:
|
||||
raise ValueError(f"Unknown result: {result}")
|
||||
print("\n\n".join(output))
|
||||
|
||||
return print_result
|
||||
|
||||
|
||||
def make_alloc_arg_and_check(
|
||||
inputs: list[np.ndarray],
|
||||
original_mod: IRModule,
|
||||
scheduled_mod: IRModule,
|
||||
trace: str,
|
||||
original_res: list[np.ndarray],
|
||||
original_run_secs: list[float],
|
||||
print_result: Callable,
|
||||
) -> tuple[Callable, Callable]:
|
||||
"""Make alloc_arg and check functions for the given inputs and collect results.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
inputs : List[np.ndarray]
|
||||
The inputs to the two modules.
|
||||
original_mod : IRModule
|
||||
The original IRModule.
|
||||
scheduled_mod : IRModule
|
||||
The scheduled IRModule.
|
||||
trace : str
|
||||
The trace of the scheduled IRModule.
|
||||
original_res : List[np.ndarray]
|
||||
The original results.
|
||||
original_run_secs : List[float]
|
||||
The original run times.
|
||||
print_result : Callable
|
||||
The print result function.
|
||||
|
||||
Returns
|
||||
-------
|
||||
f_with_args_alloc_argument : Callable
|
||||
The function to allocate arguments.
|
||||
|
||||
f_with_args_run_evaluator : Callable
|
||||
The function to run evaluator.
|
||||
"""
|
||||
|
||||
def f_with_args_alloc_argument_common(
|
||||
device: tvm.runtime.Device,
|
||||
args_info: ms.runner.rpc_runner.T_ARG_INFO_JSON_OBJ_LIST, # pylint: disable=unused-argument
|
||||
alloc_repeat: int,
|
||||
) -> list[ms.runner.rpc_runner.T_ARGUMENT_LIST]:
|
||||
"""Allocate arguments using the given inputs.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
session : RPCSession
|
||||
The RPC session.
|
||||
device : Device
|
||||
The device.
|
||||
args_info : T_ARG_INFO_JSON_OBJ_LIST
|
||||
argument information.
|
||||
alloc_repeat : int
|
||||
The number of times to repeat the allocation.
|
||||
|
||||
Returns
|
||||
-------
|
||||
args_list : List[T_ARGUMENT_LIST]
|
||||
The list of argument lists.
|
||||
"""
|
||||
return [
|
||||
[tvm.runtime.tensor(arg, device=device) for arg in inputs] for _ in range(alloc_repeat)
|
||||
]
|
||||
|
||||
def f_with_args_run_evaluator_common(
|
||||
rt_mod: tvm.runtime.Module,
|
||||
device: tvm.runtime.Device,
|
||||
evaluator_config: ms.runner.EvaluatorConfig,
|
||||
repeated_args: list[ms.runner.rpc_runner.T_ARGUMENT_LIST],
|
||||
) -> list[float]:
|
||||
"""With args function to run the evaluator
|
||||
|
||||
Parameters
|
||||
----------
|
||||
session : tvm.rpc.RPCSession
|
||||
The RPC session
|
||||
rt_mod: Module
|
||||
The runtime module
|
||||
device: Device
|
||||
The device to run the evaluator
|
||||
evaluator_config: EvaluatorConfig
|
||||
The evaluator config
|
||||
repeated_args: List[T_ARGUMENT_LIST]
|
||||
The repeated arguments
|
||||
|
||||
Returns
|
||||
-------
|
||||
costs: List[float]
|
||||
The evaluator results
|
||||
"""
|
||||
evaluator = rt_mod.time_evaluator(
|
||||
func_name=rt_mod.entry_name,
|
||||
dev=device,
|
||||
number=evaluator_config.number,
|
||||
repeat=evaluator_config.repeat,
|
||||
min_repeat_ms=evaluator_config.min_repeat_ms,
|
||||
f_preproc=(
|
||||
"cache_flush_cpu_non_first_arg" if evaluator_config.enable_cpu_cache_flush else ""
|
||||
),
|
||||
)
|
||||
|
||||
repeated_costs: list[list[float]] = []
|
||||
for args in repeated_args:
|
||||
device.sync()
|
||||
profile_result = evaluator(*args)
|
||||
repeated_costs.append(profile_result.results)
|
||||
costs = [float(cost) for cost in itertools.chain.from_iterable(repeated_costs)]
|
||||
|
||||
assert len(repeated_args) == 1, "Only support one set of arguments"
|
||||
scheduled_res = [arg.numpy() for arg in repeated_args[0]] # type: ignore
|
||||
# fetch comparison function
|
||||
passed = check_and_run(
|
||||
ARGS.check_metric_func,
|
||||
to_tvm_tensor(original_res),
|
||||
to_tvm_tensor(scheduled_res),
|
||||
)
|
||||
|
||||
print_result(
|
||||
result="pass" if passed else "wrong answer",
|
||||
original_mod=original_mod,
|
||||
scheduled_mod=scheduled_mod,
|
||||
trace=trace,
|
||||
inputs=inputs,
|
||||
original_res=original_res,
|
||||
scheduled_res=scheduled_res,
|
||||
original_run_secs=original_run_secs,
|
||||
scheduled_run_secs=costs,
|
||||
)
|
||||
|
||||
return costs
|
||||
|
||||
def f_with_args_alloc_argument_rpc(
|
||||
rpc_session: ms.runner.rpc_runner.RPCSession, # pylint: disable=unused-argument
|
||||
device: tvm.runtime.Device,
|
||||
args_info: ms.runner.rpc_runner.T_ARG_INFO_JSON_OBJ_LIST,
|
||||
alloc_repeat: int,
|
||||
) -> list[ms.runner.rpc_runner.T_ARGUMENT_LIST]:
|
||||
return f_with_args_alloc_argument_common(device, args_info, alloc_repeat)
|
||||
|
||||
def f_with_args_run_evaluator_rpc(
|
||||
rpc_session: ms.runner.rpc_runner.RPCSession, # pylint: disable=unused-argument
|
||||
rt_mod: tvm.runtime.Module,
|
||||
device: tvm.runtime.Device,
|
||||
evaluator_config: ms.runner.EvaluatorConfig,
|
||||
repeated_args: list[ms.runner.rpc_runner.T_ARGUMENT_LIST],
|
||||
) -> list[float]:
|
||||
return f_with_args_run_evaluator_common(rt_mod, device, evaluator_config, repeated_args)
|
||||
|
||||
if ARGS.rpc_config is None:
|
||||
return f_with_args_alloc_argument_common, f_with_args_run_evaluator_common
|
||||
else:
|
||||
return f_with_args_alloc_argument_rpc, f_with_args_run_evaluator_rpc
|
||||
|
||||
|
||||
def local_build_and_run(
|
||||
mod: IRModule,
|
||||
target: Target,
|
||||
device: tvm.runtime.Device,
|
||||
inputs: list[np.ndarray],
|
||||
) -> tuple[list[np.ndarray], list[float]]:
|
||||
"""Build and run the module locally.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod: IRModule
|
||||
The module to build and run
|
||||
target: Target
|
||||
The target to build the module
|
||||
device: Device
|
||||
The device to run the module
|
||||
inputs: List[np.ndarray]
|
||||
The inputs to run the module
|
||||
|
||||
Returns
|
||||
-------
|
||||
res: List[np.ndarray]
|
||||
The results of running the module
|
||||
run_secs: List[float]
|
||||
The running time of running the module
|
||||
"""
|
||||
# potential memory leak https://github.com/apache/tvm/issues/11096
|
||||
lib = tvm.compile(mod, target=target)
|
||||
tvm_inputs = [tvm.runtime.tensor(inp, device=device) for inp in inputs]
|
||||
device.sync()
|
||||
func = lib.time_evaluator(lib.entry_name, dev=device, number=ARGS.number, repeat=ARGS.repeat)
|
||||
benchmark_res = func(*tvm_inputs)
|
||||
device.sync()
|
||||
return [arg.numpy() for arg in tvm_inputs], list(benchmark_res.results)
|
||||
|
||||
|
||||
def _check_builder_result(builder_result: ms.builder.BuilderResult) -> None:
|
||||
"""Check if the builder result is defined.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
builder_result: BuilderResult
|
||||
The builder result
|
||||
"""
|
||||
assert builder_result.error_msg is None, "Builder failed: " + str(
|
||||
builder_result.error_msg if builder_result.error_msg else "Empty error message"
|
||||
)
|
||||
|
||||
|
||||
def _apply_trace(mod: IRModule, trace: Trace) -> IRModule:
|
||||
"""Apply the trace to the module.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod: IRModule
|
||||
The module to apply the trace to
|
||||
trace: Trace
|
||||
The trace to apply
|
||||
|
||||
Returns
|
||||
-------
|
||||
mod: IRModule
|
||||
The module with the trace applied
|
||||
"""
|
||||
sch = Schedule(mod)
|
||||
trace.apply_to_schedule(sch, remove_postproc=False)
|
||||
return sch.mod
|
||||
|
||||
|
||||
def _build_all_mods(
|
||||
mods: list[IRModule], builder: ms.builder.Builder, target: Target
|
||||
) -> list[ms.builder.BuilderResult]:
|
||||
"""Build all the modules.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mods: List[IRModule]
|
||||
The modules to build
|
||||
builder: Builder
|
||||
The builder to build the modules
|
||||
target: Target
|
||||
The target to build the modules
|
||||
|
||||
Returns
|
||||
-------
|
||||
builder_results: List[BuilderResult]
|
||||
The builder results
|
||||
"""
|
||||
builder_results = builder.build([ms.builder.BuilderInput(mod, target) for mod in mods])
|
||||
assert len(builder_results) == len(mods), (
|
||||
f"Unexpected number of build results, expected {len(mods)} got {len(builder_results)}"
|
||||
)
|
||||
return builder_results
|
||||
|
||||
|
||||
def _run_single_mod(
|
||||
builder_result: ms.builder.BuilderResult,
|
||||
runner: ms.runner.Runner,
|
||||
dev_type: str,
|
||||
) -> None:
|
||||
"""Run a single module.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
builder_result: BuilderResult
|
||||
The builder result
|
||||
runner: Runner
|
||||
The runner to run the module
|
||||
dev_type: str
|
||||
The device type
|
||||
"""
|
||||
runner_futures = runner.run(
|
||||
# arginfo is not used in this case so we can pass an empty list
|
||||
[ms.runner.RunnerInput(builder_result.artifact_path, device_type=dev_type, args_info=[])]
|
||||
)
|
||||
assert len(runner_futures) == 1, (
|
||||
f"Unexpected number of runner futures, expected 1 got {len(runner_futures)}"
|
||||
)
|
||||
(runner_future,) = runner_futures # pylint: disable=unbalanced-tuple-unpacking
|
||||
runner_res = runner_future.result()
|
||||
assert runner_res.error_msg is None, "Runner failed: " + (
|
||||
runner_res.error_msg if runner_res.error_msg else "Empty error message"
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function"""
|
||||
describe()
|
||||
with ms.Profiler() as profiler:
|
||||
# initialize
|
||||
target = ARGS.target
|
||||
dev_type = get_device_type(target)
|
||||
builder = ms.builder.LocalBuilder()
|
||||
database = ms.database.create(work_dir=ARGS.work_dir)
|
||||
|
||||
# collect records
|
||||
with profiler.timeit("collect records"):
|
||||
records = database.get_all_tuning_records()
|
||||
total = len(records)
|
||||
print(
|
||||
f"Total {total} records to be validated. "
|
||||
f"Collected in {float(profiler.get()['collect records']): 3.3f} sec."
|
||||
)
|
||||
|
||||
# collect unique original TIR
|
||||
with profiler.timeit("deduplicate records"):
|
||||
workloads = set()
|
||||
for record in records:
|
||||
workloads.add(OriginalModule(record.workload.mod))
|
||||
print(
|
||||
f"Total {len(workloads)} unique original TIR to validate. "
|
||||
f"Deduplicated in {float(profiler.get()['deduplicate records']): 3.3f} sec."
|
||||
)
|
||||
if ARGS.top_k < 10**9:
|
||||
print(f"Top {ARGS.top_k} records for each original TIR will be validated.")
|
||||
total = len(workloads) * ARGS.top_k
|
||||
print()
|
||||
|
||||
# validate correctness
|
||||
counter = 0
|
||||
for item in workloads:
|
||||
original_mod = item.mod
|
||||
records = database.get_top_k(
|
||||
workload=database.commit_workload(original_mod), top_k=ARGS.top_k
|
||||
)
|
||||
if len(records) < ARGS.top_k:
|
||||
total -= ARGS.top_k - len(records)
|
||||
inputs = to_numpy(check_and_run(ARGS.input_generator_func, original_mod))
|
||||
original_res, original_run_secs = local_build_and_run(
|
||||
original_mod,
|
||||
target=ARGS.baseline_target,
|
||||
inputs=inputs,
|
||||
device=get_runtime_device(ARGS.baseline_target),
|
||||
)
|
||||
scheduled_mods = [_apply_trace(original_mod, record.trace) for record in records]
|
||||
builder_results = _build_all_mods(scheduled_mods, builder, target) # type: ignore
|
||||
for i, record in enumerate(records):
|
||||
counter += 1
|
||||
print_result = print_with_counter_func(counter=counter, total=total)
|
||||
if is_failed_record(record):
|
||||
# skip failed records where run_secs is 1e9
|
||||
# these records are only negative samples for cost model
|
||||
print_result(result="skip")
|
||||
continue
|
||||
try:
|
||||
# prepare scheduled module
|
||||
scheduled_mod = scheduled_mods[i]
|
||||
# check build result
|
||||
builder_result = builder_results[i]
|
||||
_check_builder_result(builder_result)
|
||||
# fetch functions
|
||||
(
|
||||
f_with_args_alloc_argument,
|
||||
f_with_args_run_evaluator,
|
||||
) = make_alloc_arg_and_check(
|
||||
inputs,
|
||||
original_mod,
|
||||
scheduled_mod,
|
||||
str(record.trace),
|
||||
original_res=original_res,
|
||||
original_run_secs=original_run_secs,
|
||||
print_result=print_result,
|
||||
)
|
||||
# create runner
|
||||
evaluator_config = ms.runner.EvaluatorConfig(
|
||||
number=ARGS.number,
|
||||
repeat=ARGS.repeat,
|
||||
min_repeat_ms=ARGS.min_repeat_ms,
|
||||
enable_cpu_cache_flush=ARGS.cpu_flush,
|
||||
)
|
||||
if ARGS.rpc_config is not None:
|
||||
runner: ms.Runner = ms.runner.RPCRunner( # type: ignore
|
||||
ARGS.rpc_config,
|
||||
evaluator_config=evaluator_config,
|
||||
alloc_repeat=1,
|
||||
f_alloc_argument=f_with_args_alloc_argument,
|
||||
f_run_evaluator=f_with_args_run_evaluator,
|
||||
initializer=initializer,
|
||||
)
|
||||
else:
|
||||
runner: ms.Runner = ms.runner.LocalRunner( # type: ignore
|
||||
evaluator_config=evaluator_config,
|
||||
alloc_repeat=1,
|
||||
f_alloc_argument=f_with_args_alloc_argument,
|
||||
f_run_evaluator=f_with_args_run_evaluator,
|
||||
initializer=initializer,
|
||||
)
|
||||
|
||||
# run and validate
|
||||
_run_single_mod(builder_result, runner, dev_type) # type: ignore
|
||||
except Exception as e: # pylint: disable=broad-except, invalid-name
|
||||
# validation failed with exception
|
||||
print_result(
|
||||
result="exception",
|
||||
original_mod=original_mod,
|
||||
scheduled_mod=scheduled_mod,
|
||||
trace=str(record.trace),
|
||||
exception=e,
|
||||
)
|
||||
# clean up
|
||||
remove_build_dir(builder_result.artifact_path)
|
||||
print(f"Validation finished! Total time spent: {float(profiler.get()['Total']): 3.3f} sec.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user